실행컨텍스트란?

 - 스코프가 코드 실행의 범위였다면, 실행컨텍스트는 실행가능한 코드의 소유권에 관한 문제이다.

 - this라는 키워드는 현재 실행중인 컨텍스트를 참조한다.

 - 실행컨텍스트도 하나의 객체로서 var user = new User(); 의 코드 첫부분을 진입했을 때 이 코드의 실행컨텍스트는 user 가 된다.

 - 자바스크립트에서 실행컨택스트를 결정하는 방법이 복잡하다.

 

실행컨텍스트 스택(저장공간)이 존재한다.

 - 실행가능한 코드에서 다른 실행가능한 코드로 진입시, 코드를 담당할 새로운 실행컨텍스트로 진입한다.

 - 코드는 위 컨텍스트를 현재 실행 컨텍스트로 하거나, 동작중인 실행 컨텍스트를 참조하여 작동한다.

 

그럼 실행가능한 코드란?

  1. 전역코드 : 자바스크립트 프로그램이 시작되는 곳에서 수행되는 코드. 브라우저에는 Window라는 객체가 존재. 브라우저 console (개발자도구)에 입력한 var user = new User();는 전역코드이다. (함수 바깥에 존재)
  2. 함수코드 : 함수본문의 코드. 모든 함수 본문 코드가 함수코드를 의미하진 않음(실행컨텍스트가 다르게 실행되는 코드도 있음). 실행 또는 call 될 때 마다, 완전 새로운 실행컨텍스트를 생성.
  3. Eval : 내장된 eval 함수로 전달되는 문자열 값. eval또한 고유 컨택스트에 존재. 이 코드를 정확하게 모른다면 사용을 말아야 할 정도로, 신중하게 사용해야하는 코드

 

function User(name) {
	console.log('I\'m in ' + this.constructor.name + '" context. '); 
	this.name = name;
	this.speak = function () {
		console.log(this.name + ' is speaking from "' + this.constructor.name + '" context.');
		var drink = function () {
			console.log('Drinking in "' + this.constructor.name + '"');
		}
		drink();
	};
	function ask() {
		console.log('Asking from "' + this.constructor.name + '" context.');
		console.log('Who am I? "' + this.name + '"');
	}
	ask();
}
var name = 'Unknown';
var user = new User('Ted');
user.speak();

결과

결과

1번 라인 : var user = new User('Ted'); 를 인스턴스화 할 때 함수 내부로 이동하면서 실행컨텍스트가 함수의 것으로 바뀜

2번 라인 : 함수를 직접호출한 경우, 실행컨텍스트는 전역 컨텍스트가 됌

4번 라인 : Ted, User => 함수의 프로퍼티로써 메소드를 실행했기 때문에 함수를 실행컨텍스트로 사용

5번 라인 : 함수 표현식을 사용했으나, 직접 호출로 전역컨텍스트를 참조

 

함수를 내부 프로퍼티로 두면 user.speak와 같은 접근은 가능하나 내부의 변수(함수표현식)일 때는 User.speak()와 같은 호출이 불가능하다.

 

위 예제에서 보듯,

ask => Unknown, User

speak => Ted, User

위 두 함수는 같은 scope, 다른 실행컨텍스트를 가짐

 

자바스크립트 엔진관점에서 위 소스를 해석하면

1. 생성자로 호출시 실행 컨텍스트는 함수의 것으로 생성 (인스턴스화 할 때)

2. 2번 라인의 경우 1번에서 생성한 실행컨텍스트를 현재 컨텍스트로

3. 17번 라인에서 ask 함수 생성시 => 제어권은 ask에 있으나 실행컨텍스트를 바꿔서 호출하는 Function.call 에 의해 호출 된 것이 아니므로 전역이 실행 컨텍스트가 됨

4. 다시 제어권은 speak 에게 넘어가고 User 객체가 실행컨텍스트가 됨 =>Ted is speaking from "User" context.

5. drink 함수 실행시 다시 전역컨텍스트로 변경 => Drinking in Window

 

 

메소드가 호출되는 방식에 따라서도 영향을 받음(메소드를 통한 호출)

- 위 코드의 ask()를 ask.call(this);로 변경하면

결과

위 와 같이 실행 컨텍스트가 user로 변경 된 것을 알 수 있다.

- user.speak()를 user.speak.apply({name:'Jack'}); 으로 변경하면

결과

실행 컨텍스트가 {name:'Jack'} 객체로 변경 되어서 생성자는 Object로 name은 Jack으로 변경 된 것을 알 수 있다.

call, apply, bind 메소드 등을 사용하면 실행 컨텍스트를 변경할 수 있는데 이는 Function 객체에 의해 생성된 객체이고

아래 코드를 실행함으로써 확인이 가능하다.

console.log(Function.prototype.isPrototypeOf(user.speak)); // true
user.speak.hasOwnProperty('apply'); // false
user.speak.__proto__.hasOwnProperty('apply'); // true

user.speak의 프로토타입은 Function  으로 전역 컨텍스트를 참조하고 있는 객체이며

user.speak는 apply 메소드를 가지고 있지 않으나, User의 프로토타입에 apply 메소드를 가지고 있다.

 

bind 메소드는?

 - 첫번째 인자를 통해 함수의 실행 컨텍스트로 사용할 객체를 전달할 수 있고, 바인딩 될 새로운 함수를 생성

 - 나머지 매개변수로 변수 또는 메소드를 기본 할당한 새로운 함수를 생성가능

 - call 또는 apply 로 실행컨택스트를 변경하려고 해도 변경 안됨

 - bind(this)() 와 같이 즉시실행도 가능하다.

 

1. 실행 컨텍스트를 첫번 째 전달한 인자로 고정시키기

this.x = 9;
var module = {
		x: 81,
		getX: function() {return this.x;}
}; //리터럴 객체
console.log(module.getX()); //81. 실행컨텍스트는 module

var retriveveX = module.getX; //함수 자체를 할당
console.log(retriveveX()); // 9. 실행컨텍스트가 전역임

//bind 키워드로 module과 바인딩 된 새로운 함수를 생성. 이 때 실행컨텍스트를 전달한 객체로 고정
var boundGetX = retriveveX.bind(module);
console.log(boundGetX());

 

2. 인자를 고정한 채 새로운 함수를 생성하는 방법

function list() {
	return Array.prototype.slice.call(arguments); // 인자를 자동으로 넘겨 받은 키워드 arguments
}
var list1 = list(1,2,3); //[1,2,3]
console.log(list1);

//선행될 인수를 설정한 새로운 객체 생성
var leadingThirtyList = list.bind(null, 30); // 첫번째 인자가 null 이면 기존의 컨텍스트를 따르겠다는 뜻이 됌
var list2 = leadingThirtyList(); //[30]
console.log(list2)
var list3 = leadingThirtyList(1,2,3) // [30,1,2,3]
console.log(list3)

function sum(arg1, arg2) {
	return arg1 + arg2;
}

var result1 = sum(1,2); // 3

var sumOnThirty = sum.bind(null, 30);
var result2 = sumOnThirty(5); //35
console.log(result2);
var result3 = sumOnThirty(5,10) //35. 두번째로 넘겨받은 10은 무시
console.log(result3);

 

3. 일반적으로 실행컨텍스트가 전역인 setTimeout 함수와 같이 사용하곤 함

function LateBloomer() {
	console.log(Math.ceil(Math.random(1*12) + 1))
	this.petalCount = Math.ceil(Math.random(1*12) + 1);
}

//1초 지제 후 bloom 선언
LateBloomer.prototype.bloom = function () {
	window.setTimeout(this.declare.bind(this), 1000); // 이 객체의 declare를 실행시 실행 컨텍스트로 받은 객체를 사용하겠다.
}

LateBloomer.prototype.declare = function() {
	console.log('I am a beautiful flower with ' + this.petalCount + ' petals!');
}
var flower = new LateBloomer();
flower.bloom(); // I am a beautiful flower with 2 petals!

 

실행컨텍스트에 관해 정리하자면

 

  1.  생성자함수 호출시 (new) => 2~4 제외하고 생성자로 생성된 객체를 실행 컨텍스트로
  2. 직접 호출 ask() => 1,3,4 제외하고 전역 컨텍스트를 실행컨텍스트로
  3. 메소드 호출 user.speak() => 1,2,4 제외하고 메소드가 속한 객체를 실행컨텍스트로
  4. 컨텍스트 변경 호출=> ask.call, ask.apply은 전달받은 인자를 실행컨텍스트로

 

 

 


 

호이스팅이란?

자바스크립트 인터프리터가 함수 실행 전, 내부의 모든 선언(변수,함수)을 유효 범위 최상단에 선언 하는 것

 

travel = 'No plan';
var travel;
console.log(travel); //No plan

function travel() {
	console.log('Traveling');
}
travel(); //Uncaught TypeError

자바에선 컴파일 단계에서 이렇게 출력되는 이유는 호이스팅 때문이다.

 

호이스팅

 1. 함수를 스코프 최상단에 선언

 2. 변수를 함수 바로 아래에 선언

 

위 규칙을 통해 아래와 같이 변환(?)된 코드가 작동을 하는 것이다.

function travel() {
	console.log('Traveling');
}

var travel;
travel = 'No plan'; // travel 변수에 할당 된 내용이 있으므로 travel 키워드 앞에는 'No plan'이라는 객체가 할당 됨
console.log(travel); //No plan
travel(); // 에러

함수 travel의 선언을 최상단으로 하고, 그 다음 변수 선언을 이동 시켰음

아래 코드를 보자

 

function workout() {
	goToGym();
	var goToGym = function () {
		console.log('Workout in Gym A');
	}
	return;
	function goToGym() {
		console.log('Workout in Gym B');
	}
}
workout(); // Workout in Gym B

함수 goToGym이 return 아래에 있기 때문에 workout()은 Workout in Gym A을 출력할 것 같지만, 아니다.

호이스팅 때문에 아래와 같은 코드로 변환된다.

 

function workout() {
	function goToGym() {
		console.log('Workout in Gym B');
	}
	var goToGym; //변수의 선언만 위로 올라감. 허나 이 키워드엔 아무것도 할당 되지 않았기에 goToGym을 호출하면 위에 함수가 호출 될 것이다.
	goToGym();
	goToGym = function () { // 변수의 대입은 선언과 같이 위로 올라가지 않음
		console.log('Work in Gym A');
	}
	return;
}
workout(); // Workout in Gym B

호이스팅 개념에 따라 선언이 최상단으로 이동 했기 때문에 위와 같은 결과를 얻었다.

 

정리하자면

 - 자바스크립트 인터프리터는 함수 실행 전 해당 스코프의 최상단에 함수선언, 변수선언 순으로 이동 시킴

 - 그래서 return 이후에 함수를 선언해도 문제가 없다.