3. 자바스크립트의 실행컨텍스트(this), 호이스팅
실행컨텍스트란?
- 스코프가 코드 실행의 범위였다면, 실행컨텍스트는 실행가능한 코드의 소유권에 관한 문제이다.
- this라는 키워드는 현재 실행중인 컨텍스트를 참조한다.
- 실행컨텍스트도 하나의 객체로서 var user = new User(); 의 코드 첫부분을 진입했을 때 이 코드의 실행컨텍스트는 user 가 된다.
- 자바스크립트에서 실행컨택스트를 결정하는 방법이 복잡하다.
실행컨텍스트 스택(저장공간)이 존재한다.
- 실행가능한 코드에서 다른 실행가능한 코드로 진입시, 코드를 담당할 새로운 실행컨텍스트로 진입한다.
- 코드는 위 컨텍스트를 현재 실행 컨텍스트로 하거나, 동작중인 실행 컨텍스트를 참조하여 작동한다.
그럼 실행가능한 코드란?
- 전역코드 : 자바스크립트 프로그램이 시작되는 곳에서 수행되는 코드. 브라우저에는 Window라는 객체가 존재. 브라우저 console (개발자도구)에 입력한 var user = new User();는 전역코드이다. (함수 바깥에 존재)
- 함수코드 : 함수본문의 코드. 모든 함수 본문 코드가 함수코드를 의미하진 않음(실행컨텍스트가 다르게 실행되는 코드도 있음). 실행 또는 call 될 때 마다, 완전 새로운 실행컨텍스트를 생성.
- 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!
실행컨텍스트에 관해 정리하자면
- 생성자함수 호출시 (new) => 2~4 제외하고 생성자로 생성된 객체를 실행 컨텍스트로
- 직접 호출 ask() => 1,3,4 제외하고 전역 컨텍스트를 실행컨텍스트로
- 메소드 호출 user.speak() => 1,2,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 이후에 함수를 선언해도 문제가 없다.
'모던 웹 애플리케이션 개발 > 자바스크립트(JavaScript)' 카테고리의 다른 글
4. 자바스크립트 ES6 에서 추가된 문법 (0) | 2021.02.02 |
---|---|
2. 자바스크립트와 자바의 차이점 / 프로퍼티, 프로토타입, 변수 스코프 (0) | 2021.01.25 |
1. 자바스크립트와 자바의 차이점 / 객체, 객체의 생성 방법 (0) | 2021.01.25 |
댓글
이 글 공유하기
다른 글
-
4. 자바스크립트 ES6 에서 추가된 문법
4. 자바스크립트 ES6 에서 추가된 문법
2021.02.02 -
2. 자바스크립트와 자바의 차이점 / 프로퍼티, 프로토타입, 변수 스코프
2. 자바스크립트와 자바의 차이점 / 프로퍼티, 프로토타입, 변수 스코프
2021.01.25 -
1. 자바스크립트와 자바의 차이점 / 객체, 객체의 생성 방법
1. 자바스크립트와 자바의 차이점 / 객체, 객체의 생성 방법
2021.01.25