자바스크립트의 실행 컨텍스트, this 바인딩

Dohyeon Kong·2024년 3월 25일
0

JavaScript🟡

목록 보기
5/13
post-thumbnail

실행 컨텍스트(Execution Context)

실행 컨텍스트실행할 코드에 제공할 환경 정보들을 모아놓는 객체를 의미한다.
-> 객체란 실세계의 사물이나 개념을 소프트웨어 세계 내 추상화한 것을 의미한다.

JS는 동일한 환경에 있는 정보들을 모은 실행 컨텍스트를 콜스택에 쌓아올린 후 실행하여 코드의 환경과 순서를 보장할 수 있게 된다.

  • 스택(Stack)이란? 출입구가 하나뿐인 깊은 우물 같은 데이터 구조를 의미하며 비어있는 스택에 순서대로 a, b, c, d를 넣는다고 가정하면 꺼낼때는 d, c, b, a의 순서로 꺼내게 된다.

  • 스택을 사용하는 이유는 스택의 대표적인 특징인 (FILO(선입후출) : First in Last Out)가 순서를 보장해주고, 콜스택 내부에 쌓인 실행 컨텍스트의 정보를 통해 환경을 보장할 수 있다.
    (환경은 전역 변수 혹은 함수 내부의 환경이 될 수 있다.)
    => 콜스택(Call Stack)이란 자바스크립트 코드가 실행되며 생성되는 실행 컨텍스트(Execution Context)를 저장하는 자료구조를 의미한다.(콜스택 = 실행 컨텍스트를 저장하는 자료구조)

  • 하나의 실행 컨텍스트를 구성할 수 있는 방법으로 전역공간과 eval() 함수, 함수 등이 있으며 자동으로 생성되는 전역공간과 eval()함수를 제외하면 실행 컨텍스트를 구성할 수 있는 방법은 함수를 실행하는 것뿐이다.
    (ES6에서 블록에 의해서도 새로운 실행 컨텍스트가 실행된다)

  • eval()함수는 문자로 표현된 JavaScript코드를 실행하는 함수를 의미한다.
  • 전역 실행 컨텍스트

    변수 객체를 생성하는 대신 JS 구동 환경이 별도로 제공하는 객체를 의미하며 전역 객체를 활용한다. 전역 객체에는 브라우저의 window, Node.js의 global객체 등이 있다.
    자바스크립트 내장 객체(native object가 아닌 호스트 객체(host object)로 분류된다.

// 실행 컨텍스트와 콜스택
var a = 1;
function outer(){
 	function inner(){
      console.log(a);
      var a = 5;
    }
  	inner();
  	console.log(a);
}
outer();
console.log(a);
// * 실행 결과 *
// undefined
// 1
// 1

실행 컨텍스트(Execution Context)와 콜스택의 흐름

  • (1) 콜스택엔 전역 컨텍스트를 제외하곤 다른 컨텍스트가 없기에 전역 컨텍스트와 관련된 코드를 진행한다.

  • (2) 전역 컨텍스트와 관련된 코드를 진행 중 outer()함수를 호출함으로써 outer에 대한 환경 정보를 수집하여 outer 실행 컨텍스트를 생성한 후 콜스택에 담는다.
    콜스택의 맨 위에 outer 실행 컨텍스트가 있기 때문에 전역 컨텍스트와 관련된 코드실행은 일시 중단하고 outer 실행 컨텍스트와 관련된 코드, 즉 outer 함수 내부의 코드들을 순차로 실행한다.

  • (3) inner 함수의 실행 컨텍스트가 콜스택 가장 위에 담기면 outer 컨텍스트와 관련된 코드의 실행을 중단하고 inner 함수 내부의 코드를 순서대로 진행한다.

  • (4) inner 함수 내부에 a 변수에 값 5를 할당하고 나서 inner 함수의 실행이 종료되면서 inner 실행 컨텍스트가 콜스택에 제거된다. 제거된 후 아래에 있던 outer 컨텍스트가 콜스택의 맨위에 존재하게 되고 중단했던 outer() 함수를 실행한다.

  • (5) a변수의 값이 출력되면 outer 함수의 실행이 종료되어 outer 실행 컨텍스트가 콜스택에서 제거되고, 콜스택에는 전역 컨텍스트만 남아 있게 된다.

  • (6) 실행을 중단했던 전역 컨텍스트를 실행한다. a변수의 값을 출력하고 나면 전역공간에 더는 실행할 코드가 남아 있지 않아 전역 컨텍스트도 제거되어 콜스택에 아무것도 남아있지 않는 상태로 종료된다.

실행 컨텍스트(Execution Context)에 담기는 내부 정보

  • VariableEnvironment
  • LexicalEnvironment
  • ThisBinding

VariableEnvironment와 LexicalEnvironment는 현재 컨텍스트 내 식별자들에 대한 정보 + 외부 환경 정보, 선언 시점의 스냅샷으로, VariableEnvironment는 변경사항이 반영되지 않는 초기 상태를 유지를 목적으로 LexicalEnvironment는 초기 상태에서 이후 변경사항이 반영된다.

P.S) 스냅샷(SnapShot) : 사진을 찍듯이 특정 시간에 데이터 저장 장치의 파일 시스템을 포착해 별도의 파일이나 이미지로 저장 및 보관하는 기술을 의미한다.

VariableEnvironment와 LexicalEnvironment 내부 구성

  • environmentRecord
  • outerEnvironmentReference

VariableEnvironment와 LexicalEnvironment의 구성은 매개변수명, 변수의 식별자, 선언한 함수의 함수명 등을 수집하는 environmentRecord와 바로 직전 컨텍스트의 LexicalEnvironment 정보를 참조하는 outerEnvironmentReference로 구성된다.

enviromentRecord

현재 컨텍스트와 관련된 코드의 식별자 정보들이 저장된다.

  • 저장되는 코드의 식별자 정보
    - 컨텍스트를 구성하는 함수에 지정된 매개변수 식별자
    - 선언한 함수가 있을 경우 선언한 함수 그 자체
    - var로 선언된 변수의 식별자
  • enviromentRecord는 컨텍스트 내부 전체를 처음부터 끝까지 쭉 훑어나가며 순서대로 수집한다.

outerEnvironmentReference

스코프 체인을 가능하게 하는 기능을 가지고 있다.

  • 스코프(Scope)란 식별자에 대한 유효범위를 의미한다. 예시로 경계 A의 외부에서 선언한 변수는 A 외부 및 내부 둘다 접근이 가능하지만 A의 내부에 선언한 변수는 오직 A의 내부에서만 접근이 가능하다.
  • JS는 전역공간을 제외하면 오직 함수에 의해서만 스코프가 생성된다.
  • 스코프 체인(Scope chain)이란 식별자의 유효범위를 안에서부터 바깥으로 차례로 검색해나가는 것을 의미한다. => 이를 가능케 하는 것이 바로 LexicalEnvironment의 outerEnviromentReference이다.
    - outerEnvironmentReference는 현재 호출된 함수가 선언될 당시의 LexicalEnvironment를 참조한다.

ThisBinding : 식별자가 바라봐야 할 대상 객체


This

  • this란 '이것' 이라는 의미이며 JavaScript의 예약어를 의미한다.
  • 함수가 실행될 때 함수 내부에서 사용되는 값을 의미한다.
  • 함수의 객체 취급을 받는 누구를 지칭하는 것인지 알려주는 것을 의미한다.
  • 함수를 호출한 컨텍스트를 가리킨다.
  • 자바스크립트 엔진에 의해 암묵적으로 생성된다.
  • this는 코드 어디서든 참조할 수 있다.

결국 this는 '함수를 호출한 객체'를 의미한다.

  • 함수를 호출하면 인자와 this가 암묵적으로 함수 내부에 전달된다.
  • 함수 내부에서 인자를 지역 변수처럼 사용할 수 있는 것처럼, this도 지역 변수처럼 사용할 수 있다.
  • 크게 전역에서 사용할 때함수 안에서 사용할 때로 나누어 진다.

바인딩(Binding)

바인딩이란? '묶다'라는 사전적 의미로 코딩에서의 바인딩은 두 데이터 혹은 소스를 일치시키는 방법을 의미한다.

  • 화면에 보이는 데이터와 메모리에 있는 데이터(여러개의 JS객체)를 일치시키는 것을 의미한다.
  • 변수와 값, 함수와 매개변수, 메소드와 객체 등을 연결하는 프로세스를 의미한다.
  • 변수 바인딩(Variable Binding)
    - 변수를 값에 연결하는 프로세스이며, 변수를 선언하고 값을 할당할 때 일어난다.
	let x = 10;
  • 함수 바인딩(Function Binding)
    - 함수와 함수명 간의 연결을 의미하며, 함수를 선언하면 해당 함수명이 함수 객체에 바인딩된다.
	function greet() {
    	console.log('Hello!');
	}
  • 매개변수 바인딩(Parameter Binding)
    - 함수를 호출할 때 전달된 값과 함수 내부의 매개변수를 연결하는 프로세스를 의미한다.
	function greet() {
    	console.log('Hello!');
	}	
  • 메소드 바인딩(Method Binding)
    - 객체의 메소드가 해당 객체에 바인딩된다. 메소드를 호출하면 메소드 내부에서 this키워드를 통해 해당 객체에 접근할 수 있다.
	const person = {
    	name: 'Alice',
    	greet: function() {
       		console.log(`Hello, my name is ${this.name}.`);
   		}
	};

this 바인딩 종류

this를 전역에서 사용한 경우

  • 브라우저라는 JS 런타임의 경우 this는 항상 window라는 전역 객체를 참조한다.
  • 전역 객체란 전역 범위에 항상 존재하는 객체를 의미한다.
  • 브라우저라는 JS 런타임에서 모든 변수, 함수는 window라는 객체의 프로퍼티와 메서드이며 Node.js 환경에서는 global을 의미한다.
console.log(this); 	  //alert : f(), atob : f(), blur : f(), btoa: f(), ...}
console.log(window); //alert : f(), atob : f(), blur : f(), btoa: f(), ...}
console.log(this === window); //true
// 다음 코드는 전역 실행 컨텍스트를 가리킨다.
var a = 1;
console.log(a); // 출력 : 1
console.log(this.a) //출력 : 1
// 자바스크립트의 모든 변수는 실은 객체의 프로퍼티로서 동작한다.
// 현재 this는 전역 객체를 의미하기 때문에 this.a == window.a와 값이 같다.

this를 함수 내부에서 사용한 경우

  • 함수는 전역에 선언된 일반 함수와 객체 안에 메소드로 크게 구분한다.
  • 객체안에 선언된 함수를 전역에 선언된 함수와 구분하기 위해 메서드라고 부른다.
  • 즉, 모든 함수는 객체 내부에 있다고 말할 수 있다.
  • 이때 말하는 this는 현재 함수를 실행하고 있는 그 객체를 참조하는 것을 의미한다.
  • 결국 함수 내부에서 this의 값은 함수를 호출하는 방법에 의해 바뀌게 되며, 엄격모드에 따라 참조값이 달라지게 된다. (=this를 부르는 상황마다 다르게 동작한다는 의미이다.)
  • 엄격 모드에서 일반 함수 내부의 this는 undefined가 바인딩된다.

P.S) 엄격모드란 ? 자바스크립트가 묵인했던 에러들의 에러 메시지를 발생시키는 모드를 의미한다.

함수 호출 방식에 따른 this 바인딩의 종류

  • 기본 바인딩
  • 암시적 바인딩
  • 명시적 바인딩
  • new 바인딩

1. 기본 바인딩

  • 함수 단독 호출시에는 Global Binding이다.
  • 브라우저 상에서는 window 객체를 가리킨다.
// 기본 바인딩 예제 코드
function test(){
 	console.log(this); 
}
test(); // window

// 함수 단독 호출 시 Global Binding이다.

2. 암시적 바인딩(implicit binding)

  • 객체가 메서드로 호출될 때 사용되는 바인딩을 의미한다.
  • 함수 호출 시 함수 내부에서 사용되는 this 키워드가 동적으로 바인딩되는 개념을 의미한다.
  • 함수를 호출할 때 함수가 속한 객체의 컨텍스트를 의미하며 this를 설정하는 것을 의미한다.
  • 함수가 다른 객체 내에서 사용될 때도 같은 함수를 재사용할 수 있다는 장점이 있다.
// 예시1
const obj = {
 	name : "kim",
  	getName() {
  		return this.name;
	}
   // 현재 this가 가리키는 것은 obj 객체이므로 obj.name을 return값으로 주는 것과 같다.
}
console.log(obj.getName());
// 예시2
const person = {
  name: 'John',
  greet: function() {
    console.log(`Hello, my name is ${this.name}.`);
  }
};

person.greet(); // 출력: Hello, my name is John.
// 함수가 객체의 매소드가 아닌 전역스코프에서 호출한 예시, this는 전역 개체를 가리키게 된다.
const greet = person.greet;
greet(); // 출력: Hello, my name is undefined.

3. 명시적 바인딩

  • apply()/call()메서드와 bind()메서드가 존재한다.

apply()/call()

  • 함수 호출시 첫번째 인자로 this로 사용할 객체를 전달한다.
  • 함수의 실행값을 반환한다.
  • 두 방식은 동작 방식이 동일하며 인수를 전달하는 과정에서 차이점이 발생한다.
  • null이나 undefined를 넣으면 자동적으로 this는 global객체를 가리킨다.
  • call은 함수에 사용될 인자들을 ","로 구분지어 넣어주고, apply는 인자들을 배열형태로 넣언다.
	function introduce(name, interest){
		console.log('Hello My name is ${name} and I like 				${interest},');
	}
	const user = {
		name : 'Alice',
		interest : 'Javascript'
	};
	//call 사용
	introduce.call(user, user.name, user.interest);
	
	// apply 사용
	introduce.apply(user,[user.name, user.interest]);
   

call()와 apply() 메서드를 선택하는 기준 & 속도 차이

  • 함수에 전달해야 하는 인수의 수가 고정되어 있는 경우에는 call()메서드가 유리하며 인수의 수가 가변적이거나 배열 형태로 전달되어야 하는 경우에는 apply()메서드가 유리하다.
  • 성능차이는 서로 미미하며 대부분의 상황에서는 무시할 수 있다. 하지만 일반적으로 인수를 배열로 전달하는 apply()메서드와 달리 call()메서드가 함수에 인수 목록을 전달할 때 각 인수를 개별적으로 전달하기 때문에 call()메서드가 apply()메서드보다 약간 빠르다.

bind()

  • 첫번쨰 인자값으로 전달된 객체에 this를 고정한 새로운 함수를 반환한다.
  • bind를 통해 생성된 함수의 this는 최초의 바인딩된 객체로 고정된다.
  • 다시 명시적으로 바인딩해도 this가 바뀌지 않는다.
  function greet(){
	console.log('Hi, I am ${this.name}.');
  }
  const user = {
	name : 'Bob'
  }
  const userGreet = greet.bind(user);
  userGreet();

4. new 바인딩

  • 생성자 함수를 사용하여 새로운 객체를 생성시 발생하는 형태의 바인딩을 의미한다.
  • 새 생성한 객체에 자동으로 bind된다.
  function Person(name, age){
	this.name = name;
	this.age = age;
  }	
  const alice = new Person('Alice', 30);
  console.log(alice) // Person {name : 'Alice', age : 30}   

화살표 함수 this

  • 함수가 호출되는 방식에 따라 this가 결정된다.
  • 선언될 당시의 상위 Scope의 this를 참조한다.
  • this바인딩 자체가 없기 대문에 call(), apply(), bind() 모두 사용 불가능하다.
  • 접근하고자 하면 스코프체인상 가장 가까운 this에 접근하게 된다.
// 화살표 함수 내부에서의 this
const obj = {
 name : "Kim",
 getName : () =>{
  	return this.name; 
 }
}
console.log(obj.getName());
// 화살표 함수 내부에서의 this
var obj = {
  outer: function(){
    consol.log(this);
    
    var innnerFunc = () => {
      console.log(this);
    };
    innerFunc();
  }
};
obj.outer();

우선순위

  • this는 부르는 상황마다 다르게 동작한다.
  • new 바인딩 > 암시적 바인딩 > 명시적 바인딩 > 기본 바인딩 순이다.

참조📜

  • 코어 자바스크립트 - 정재남 지음-
profile
천천히, 꾸준히, 그리고 끝까지

0개의 댓글