실행 컨텍스트와 자바스크립트의 동작 원리

y0ung·2021년 1월 21일
0

JavaScript

목록 보기
16/20
post-thumbnail

실행 컨텍스트

실행 컨텍스트는 자바스크립트가 왜 그렇게 동작하는 지 보여주는 것이다.

let name = 'young';

function hello(word){
  console.log(`${word} ${name}`) // hello young
}

function say(){
  let name = 'olive'
  console.log(name); // olive
  hello('hello')
}

say()

위의 코드에서 console은 순서대로 olive, hello young 이 찍힌다.

코드를 실행(브라우저가 스크립트를 로딩해서 실행하는 것)하는 순간 모든 것을 포함하는 전역 컨텍스트가 생긴다. 모든 것을 관리 하는 환경이다. 페이지가 종료될 때까지 유지된다. 전역 컨텍스트 말고도 함수 컨텍스트가 있는데, 자바스크립트는 함수 스코프를 따른다. 즉 함수를 호출할 때 마다 함수 컨텍스트가 하나씩 더 생긴다.

컨텍스트의 원칙 네가지

  • 전역 컨텍스트 하나 생성 후, 함수 호출 시마다 컨텍스트가 생긴다.
  • 컨텍스트 생성 시 컨텍스트 안에 변수객체(arguments, variable), scope chain, this가 생성 된다.
  • 컨텍스트 생성 후 함수가 실행되는데, 사용되는 변수들은 변수 객체 안에서 값을 찾고, 없다면 스코프 체인을 따라 올라가며 찾는다.
  • 함수 실행이 마무리되면 해당 컨텍스트는 사라진다.(클로저 제외)
    페이지가 종료되면 전역 컨텍스트가 사라진다.

전역 컨텍스트와 함수 컨텍스트

전역 컨텍스트

전역 컨텍스트가 생성된 후 변수객체, scope chain, this가 들어온다. 전역 컨텍스트는 arguments(함수의 인자)가 없다. variable은 해당 스코프의 변수들 이다. name, hello, say가 있다.

scope chain(스코프 체인, 자신과 상위 스코프들의 변수객체이다)은 자기 자신인 전역 변수 객체이다. this는 따로 설정 되어 있지 않으면 window이다. this를 바꾸는 방법이 바로 new를 호출하는 것이다(생성자 함수).

this란 무엇인지 자세히 보고 싶다면 여기

'전역 컨텍스트' : {
  변수객체: {
  	arguments: null,
      variable:['name','hello','say']
  },
    
  scopeChain: ['전역 변수객체'],
  this: window,
}

코드를 위에서 부터 샐행하는데. hellosay는 호이스팅 때문에 선언과 동시에 대입된다. 그후에 variable의 name은 'young'이 된다.

variable:[{name:'young'},{hello: Function},{say : Function}]

함수 컨텍스트

함수 say()를 호출하는 순간 새로운 컨텍스트인 say함수 컨텍스트가 생긴다.

전역 컨텍스트는 그대로 가지고 있고, argument는 없다. variable도 name뿐이다.
scope chain은 say 변수객체와 상위의 전역 변수 객체 이다. this도 아직 까지는 window를 가리킨다.

say 컨텍스트 생성

'say 컨텍스트': {
  변수객체:{
  	arguments: null,
    variable:['name']
  },
  
  scopeChain:['say 변수 객체','전역 변수 객체'],
  this: window,
}

say를 호출한 후 위에서 부터 차례로 코드를 실행한다.

  1. variable:['name']에 olive를 대입
  2. console.log(name) 의 name변수는 say컨텍스트 안에서 찾는다. olive가 콘솔에 찍힌다.
  3. hello('hello') 실행 하는데 say컨텍스트 안에서 hello변수를 찾을수 없으므로 scope chain을 따라 올라가 상위 변수 객체에서 찾는데 say함수의 상위 변수 객체인 전역 변수 객체에서 찾는다. 전역에 hello라는 함수가 있어 호출한다.

hello 컨텍스트 생성

'hello 컨텍스트': {
  변수객체: {
    arguments: [{ word : 'hello' }],
    variable: null,
  },
    
  scopeChain: ['hello 변수객체', '전역 변수객체'],
  this: window,
}

say()함수에서 hello('hello')함수를 호출한 후 위에서 부터 차례로 코드를 실행한다.

1.console.log(`${word} ${name}`) 에서 word와 name 변수는 hello 컨텍스트 안에서 찾는다. arguments에 word = 'hello' 로 찾을수 있다. name은 hello 변수 객체에 없으므로 scope chain을 따라 전역 스코프에서 찾는다.
2. 전역 변수객체로 올라가면 variable 에 name은 'young'으로 되어 있다. 그래서 결과는hello olive가 된다.

wow의 함수 종료후 hello컨텍스트가 사라지고, say컨텍스트도 전역 컨텍스트도 사라지게 된다.

호이스팅

호이스팅이란 변수를 선언하고 초기화 했을 때 선언부분이 최상단으로 끌어 올려지는 것이다.

호이스팅이 무엇인지 자세히 알고 싶다면 여기

함수 표현식이 아닌 함수 선언식일 경우에는 식 자체가 통째로 끌어 올려진다.

console.log(say); // undefined

sayHello(); // 'hello'

function sayHello(){
  console.log('hello') // hello
}

let say = 'hello'

위의 코드는 선언보다 호출을 먼저 했다. 하지만 에러 없이 작동한다. 이유는 변수 선언과 함수 선언식이 최상단으로 끌어 올려졌기 때문이다. 위의 코드를 호이스팅 한 결과는 다음과 같다

function sayHello() {
  console.log('hello');
}

let say;

console.log(say);

sayHello();

say = 'hello';

하지만 같은 함수여도 함수 표현식으로 선언한 경우에는 에러가 발생한다.

sayHello();
sayYeah(); // 에러
var sayYeah = function() {
  console.log('yeah');
}
function sayHello() { // 선언과 동시에 초기화
  console.log('hello');
}

위의 코드의 실행환경을 살펴보면 먼저 실행시 전역 컨텍스트가 생성된다. sayHello함수는 함수 선언 식이므로 컨텍스트 생성 후 바로 대입 된다.

'전역 컨텍스트': {
  변수객체: {
    arguments: null,
    variable: [{ sayHello: Function }, 'sayYeah'],
  },
  scopeChain: ['전역 변수객체'],
  this: window,
}

컨텍스트 생성 및 코드가 순차적으로 실행되는데 sayYeah는 대입되기 전에 호출해서 에러가 난다.

클로저

비공개 변수를 가질수 잇는 환경에 있는 함수가 클로저 이다. 비공개 변수는 클로저 함수 내부에 생성한 변수도 아니고 , 매개 변수도 아닌 변수를 의미하낟. 클로저를 말할 때는 스코프/ 컨텍스트/ 비공개 변수와 함수의 관계를 항상 같이 말해주어야 한다.

클로저란 무엇인지 자세히 보고 싶다면 여기

let makeClosure = function () {
  let name = 'young';
  return function () {
    console.log(name);
  }
};

let closure = makeClosure(); // function () { console.log(name); }

closure(); // 'young';

closure 함수 안에는 console.log(name)이 있다. name은 closure 함수의 매개 변수도 아니고, closure 함수 내부에서 생성한 변수도 아니다. 이런것이 바로 비공개 변수 이다. function () {console.log(name);}는 name변수나, name변수가 있는 scope에 대해 클로저라고 부를수 있다.

이걸 컨텍스트로 분석해 보겠다. 전역 컨텍스트 생성후, makeClosure함수 호출시 makeClosure 컨텍스트도 만들어 진다.

"전역 컨텍스트": {
  변수객체: {
    arguments: null,
    variable: [{ makeClosure: Function }, 'closure'],
  },
  scopeChain: ['전역 변수객체'],
  this: window,
}
"makeClosure 컨텍스트": {
  변수객체: {
    arguments: null,
    variable: [{ name: 'young' }],
  },
  scopeChain: ['makeClosure 변수객체', '전역 변수객체'],
  this: window,
}

let closure = makeClosure(); 할때의 상황을 주목해보자. 익명함수를 return하는데 그 function 선언 시의 scope chain은 lexical scoping을 따라서 ['makeClosure 변수객체', '전역 변수객체']를 포함한다. 따라서 closure을 호출할 대 컨텍스트는 다음과 같다.

"closure 컨텍스트":  {
  변수객체: {
    arguments: null,
    variable: null,
  scopeChain: ['closure 변수객체', 'makeClosure 변수객체', '전역 변수객체'],
  this: window,
}

따라서 closure함수에서 scope chain을 통해 makeClosure의 name변수에 접근 할 수 있다.


출처

https://www.zerocho.com/category/JavaScript/post/5741d96d094da4986bc950a0

profile
어제보다는 오늘 더 나은

0개의 댓글