게시글

강좌21 - JavaScript - 5년 전 등록 / 8달 전 수정

실행 컨텍스트

클로저와 호이스팅

안녕하세요. 이번 시간에는 범위에 이어 실행 컨텍스트클로저에 대해서 살펴보겠습니다. 제가 지난 시간에 실행 컨텍스트가 제일 중요하다고 하면서 강좌를 마쳤습니다. lexical scoping과 이것만 이해하면 자바스크립트를 이해하는데 필수적인 개념인 호이스팅, 클로저를 모두 분석할 수 있습니다.

실행 컨텍스트

실행 컨텍스트는 자바스크립트가 왜 그렇게 동작하는 지 여러분께 설명해줍니다. 개념이 상당히 복잡하기 때문에 입문자분들도 이해하기 쉽도록 최대한 쉽게 알려드리기 위해 노력하겠습니다. 따라서 몇 과정은 실제 엔진 동작보다 간소하게 설명해드릴 겁니다. 예를 들면 활성 객체 같은 것은 여기서 설명하지 않겠습니다.

컨텍스트는 한국말로 번역하면 문맥입니다. 쉽게 코드의 실행 환경이라고 이해하시면 될 거 같습니다. 어려워도 포기하지 않고 끝까지 읽으시면 이해가 될 겁니다. 포기하지 마세요! 차분히 순서를 따라가시면 됩니다.

var name = 'zero'; // (1)변수 선언 (6)변수 대입
function wow(word) { // (2)변수 선언 (3)변수 대입
  console.log(word + ' ' + name); // (11)
}
function say () { // (4)변수 선언 (5)변수 대입
  var name = 'nero'; // (8)
  console.log(name); // (9)
  wow('hello'); // (10)
}
say(); // (7)

먼저 어떻게 console이 찍힐지 생각해보세요. 주석 괄호 안의 숫자 순으로 실행됩니다. 전 시간 lexical scoping을 기억하신다면 결과가 nero, hello zero라는 걸 아실 겁니다.(답을 못 맞췄거나 lexcial scoping을 모르신다면 꼭 이전 강좌 를 보고 오세요) 이것이 어떻게 실행되는지 내부를 살펴보죠.

일단 처음 코드를 실행(여기서 실행은 브라우저가 스크립트를 로딩해서 실행하는 걸 말합니다)하는 순간 모든 것을 포함하는 전역 컨텍스트가 생깁니다. 모든 것을 관리하는 환경입니다. 페이지가 종료될 때까지 유지됩니다. 전역 컨텍스트 말고도 함수 컨텍스트가 있는데요. 전 시간에 자바스크립트는 함수 스코프를 따른다고 했죠? 함수를 호출할 때마다 함수 컨텍스트가 하나씩 더 생깁니다. 컨텍스트의 원칙 네 가지를 알려드리겠습니다.

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

이제 아까 코드를 이 원칙들에 따라서 실행해보겠습니다.

전역 컨텍스트

전역 컨텍스트가 생성된 후 두 번째 원칙에 따라 변수객체, scope chain, this가 들어옵니다. 전역 컨텍스트는 arguments(함수의 인자를 말합니다)가 없고요. variable은 해당 스코프의 변수들입니다. name, wow, say가 있네요.

scope chain(스코프 체인, 자신과 상위 스코프들의 변수객체입니다)은 자기 자신인 전역 변수객체입니다. this는 따로 설정되어 있지 않으면 window입니다. this를 바꾸는 방법이 바로 new를 호출하는 겁니다. (또는 함수에 다른 this 값을 bind할 수도 있습니다) 일반 함수의 this가 왜 window인지 아시겠죠? 원래 기본적으로 window고 new나 bind같은 상황에서 this가 바뀌는 겁니다.

이걸 객체 형식으로 표현해보겠습니다.

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

이제 코드를 위에서부터 실행하는데요. wow랑 say는 호이스팅 때문에 선언과 동시에 대입이 됩니다. 호이스팅은 나중에 설명드리니 잠시 기다려주세요. 그 후 variable의 name에 'zero'가 대입됩니다.

variable: [{ name: 'zero' }, { wow: Function }, { say: Function }]

함수 컨텍스트

그 후 (7)번에서 say();를 하는 순간 새로운 컨텍스트인 say 함수 컨텍스트가 생깁니다. 아까 전역 컨텍스트는 그대로 있고요. arguments는 없고, variable은 name뿐이네요. scope chain은 say 변수객체와 상위의 전역 변수객체입니다. this는 따로 설정해준 적이 없으니까 window입니다.

'say 컨텍스트': {
  변수객체: {
    arguments: null,
    variable: ['name'], // 초기화 후 [{ name: 'nero' }]가 됨
  },
  scopeChain: ['say 변수객체', '전역 변수객체'],
  this: window,
}

say를 호출한 후 위에서부터 차례대로((8)~(10) 실행하는데요. variable의 name에 nero를 대입해주고 나서 console.log(name);이 있습니다. name 변수는 say 컨텍스트 안에서 찾으면 됩니다. variable에 name이 nero라고 되어 있네요. name이 콘솔에 찍힙니다. 그 다음엔 wow('hello')가 있는데요. say 컨텍스트 안에서 wow 변수를 찾을 수 없습니다. 찾을 수 없다면 scope chain을 따라 올라가 상위 변수객체에서 찾습니다. 그래서 전역 변수객체에서 찾습니다. 전역 변수객체의 variable에 wow라는 함수가 있네요. 이걸 호출합니다.

(10)번에서 wow함수가 호출되었으니 wow 컨텍스트도 생기겠죠? arguments는 word = 'hello'고, scope chain은 wow 스코프와 전역 스코프입니다. 여기서 중요한 게 lexical scoping에 따라 wow 함수의 스코프 체인은 선언 시에 이미 정해져 있습니다. 따라서 say 스코프는 wow 컨텍스트의 scope chain이 아닙니다. variable은 없고, this는 window입니다.

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

이제 컨텍스트가 생긴 후 함수가 실행 됩니다. say 함수는 아직 종료된 게 아닙니다. wow 함수 안에서 console.log(word + ' ' + name);이 있는데요. word랑 name 변수는 wow 컨텍스트에서 찾으시면 됩니다. word는 arguments에서 찾을 수 있고, name은 wow 변수객체에는 값이 없으니, scope chain을 따라 전역 스코프에서 찾으시면 됩니다. 전역 변수객체로 올라가니 variable에 name이 zero라고 되어 있네요. 그래서 hello zero가 되는 겁니다. hello nero가 아니라요. wow 컨텍스트에 따르면 wow 함수는 애초에 say 컨텍스트와 일절 관련이 없었던 겁니다.

이제 wow 함수 종료 후 wow 컨텍스트가 사라지고, say 함수의 실행이 마무리됩니다. 따라서 say 컨텍스트도 사라지고, 마지막에 전역 컨텍스트도 사라집니다. 함수 실행, 변수 선언 등 모든 게 다 논리적입니다. 그래서 컨텍스트 개념을 이해하면 자바스크립트의 모든 문제들을 풀 수 있습니다. 추가적으로 이벤트 루프까지 아신다면 완벽합니다. 이벤트 루프 강좌도 꼭 봐주세요.

호이스팅

이제 함수 호이스팅 현상을 컨텍스트로 분석해볼까요? 호이스팅이란 변수를 선언하고 초기화했을 때 선언 부분이 최상단으로 끌어올려지는 현상을 의미합니다. (초기화 또는 대입 부분은 그대로 남아있습니다) 아래처럼 sayWow처럼 함수 표현식이 아니라 함수 선언식일 때는 식 자체가 통째로 끌어올려집니다.

console.log(zero); // 에러가 아니라 undefined
sayWow(); // 정상적으로 wow
function sayWow() {
  console.log('wow');
}
var zero = 'zero';

위의 코드는 선언보다 호출을 먼저 하기 때문에 얼핏 보기에 말이 안 되는 것처럼 보입니다. 하지만 에러 없이 정상 작동합니다. 변수 선언과 함수 선언식이 최상단으로 끌어올려졌기 때문이죠. 위의 코드는 다음과 같습니다.

function sayWow() {
  console.log('wow');
}
var zero;
console.log(zero);
sayWow();
zero = 'zero';

하지만 같은 함수여도 함수 표현식으로 선언한 경우에는 에러가 발생합니다. 아래 예시를 보시죠.

sayWow(); // (3)
sayYeah(); // (5) 여기서 대입되기 전에 호출해서 에러
var sayYeah = function() { // (1) 선언 (6) 대입
  console.log('yeah');
}
function sayWow() { // (2) 선언과 동시에 초기화(호이스팅)
  console.log('wow'); // (4)
}

일단 처음 실행 시는 전역 컨텍스트가 먼저 생성되겠죠? sayWow 함수는 함수 선언식이므로 컨텍스트 생성 후 바로 대입됩니다.

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

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

클로저

지난 시간에 라이브러리를 만들 때 사용한다는 IIFE가 사실 클로저를 활용한 패턴입니다. IIFE가 모두 클로저인 게 아니고요. 비공개 변수를 가질 수 있는 환경에 있는 함수가 클로저입니다. 비공개 변수는 클로저 함수 내부에 생성한 변수도 아니고, 매개변수도 아닌 변수를 의미합니다. 클로저를 말할 때는 스코프/컨텍스트/비공개 변수와 함수의 관계를 항상 같이 말해주어야 합니다.

var makeClosure = function() {
  var name = 'zero';
  return function () {
    console.log(name);
  }
};
var closure = makeClosure(); // function () { console.log(name); }
closure(); // 'zero';

closure 함수 안에 console.log(name)이 있는데요. name은 closure 함수의 매개변수도 아니고, closure 함수 내부에서 생성한 변수도 아닙니다. 바로 이런 것이 비공개 변수입니다. function() { console.log(name) }은 name 변수나, name 변수가 있는 스코프에 대해 클로저라고 부를 수 있습니다. 수학적으로는 자유변수라고도 합니다.

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

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

주목할 점은 closure = makeClosure()할 때의 상황입니다. function을 return하는데 그 function 선언 시의 scope chain은 lexical scoping을 따라서 ['makeClosure 변수객체', '전역 변수객체']를 포함합니다. 따라서 closure을 호출할 때 컨텍스트는 다음과 같습니다.

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

따라서 closure 함수에서 scope chain을 통해 makeClosure의 name 변수에 접근할 수 있습니다. 클로저를 활용한 유명한 카운터 예제를 보시죠.

var counter = function() {
  var count = 0;
  function changeCount(number) {
    count += number;
  }
  return {
    increase: function() {
      changeCount(1);
    },
    decrease: function() {
      changeCount(-1);
    },
    show: function() {
      alert(count);
    }
  }
};
var counterClosure = counter();
counterClosure.increase();
counterClosure.show(); // 1
counterClosure.decrease();
counterClosure.show(); // 0

counter 함수를 호출 할 때, counterClosure 컨텍스트에 counterClosure와 counter가 담긴 scope chain이 생성됩니다. 그렇게 되면 이제 counterClosure에서 계속 count로 접근할 수 있는 거죠. return 안에 들어 있는 함수들은 count 변수나, changeCount 함수 또는 그것들을 포함하고 있는 스코프에 대한 클로저라고 부를 수 있습니다.

이런 방식으로 비공개 변수를 만들어 활용할 수 있습니다. 비공개 변수이기 때문에 남들이 조작할 걱정은 없죠. 프로그램 사용자는 여러분이 공개한 메소드만 사용해야합니다. 사용자가 예상을 뒤엎는 행동을 하는 것을 막을 수 있죠. 꼭 알아두어야 할 점은 절대로 사용자를 믿어서는 안됩니다. 무슨 짓을 할 지 모르거든요. 해킹을 시도할 수도 있고, 프로그램에 버그를 만들 수도 있습니다. 특히 서버와 연결되어 있는 경우는 더 조심해야하죠. 그렇기때문에 항상 사용자가 할 수 있는 모든 행동과 일어날 수 있는 경우의 수를 통제하고 있어야합니다. 자바스크립트에서 사용자를 통제하기 위한 기본적인 방법이 바로 클로저입니다.

단점으로는 잘못 사용했을 시 성능 문제와 메모리 문제가 발생할 수 있습니다. closure의 비공개 변수는 자바스크립트에서 언제 메모리 관리를 해야할 지 모르기 때문에 자칫 메모리 낭비로 이어질 수 있습니다. 프로그램을 만들면서 메모리 문제가 발생한다면 클로저를 의심해보세요. 또한 scope chain을 거슬러 올라가는 행동을 하기 때문에 조금 느립니다.

많이들 하는 질문이 이벤트리스너를 for문으로 연결했을 때 왜 오류가 나는지입니다. 바로 클로저를 사용하지 않아서 그렇습니다.

for (var i = 0; i < 5; i++) {
  $('#target' + i).on('click', function() {
    alert(i);
  });
}

라는 코드가 있을 때 대충 보면 #target0부터 #target4까지 제대로 이벤트리스너가 연결된 것 같죠? #target0부터 #target4까지 각각 이벤트리스너가 연결되기는 했지만 클릭 시 alert 값이 모두 5입니다. 이 문제의 원인은 컨텍스트에 대한 이해 부족입니다. lexical scoping 에 따라 함수는 선언할 때 스코프가 생성됩니다. 즉 이벤트리스너 안의 i는 외부의 i를 계속 참조하고 있는 겁니다. i는 반복문 종료 후 최종적으로 5가 되기 때문에 결국 alert 결과가 모두 5가 됩니다.

for (var i = 0; i < 5; i++) {
  (function(j) {
    $('#target' + j).on('click', function() {
      alert(j);
    });
  })(i);
}

이렇게 IIFE를 사용하여 클로저를 만들어주면 j 값은 i에 해당하는 숫자로 고정되기 때문에 해결됩니다(고정된 j 대한 클로저인 function을 만드는 셈입니다). 이번 시간은 많이 어려웠을 겁니다. 다음 시간에는 쉬어가는 시간으로 JSON 객체에 대해 짧게 알아보고 가겠습니다!

조회수:
0
목록
투표로 게시글에 관해 피드백을 해주시면 게시글 수정 시 반영됩니다. 오류가 있다면 어떤 부분에 오류가 있는지도 알려주세요! 잘못된 정보가 퍼져나가지 않도록 도와주세요.
Copyright 2016- . 무단 전재 및 재배포 금지. 출처 표기 시 인용 가능.

댓글

44개의 댓글이 있습니다.
4달 전
한가지 궁금한점이 더 생겨서 질문 올립니다. 한번에 질문하지 못해서 죄송합니다.

for (var i = 0; i < 5; i++) {
(function(j) {
$('#target' + j).on('click', function() {
alert(j);
});
})(i);
}

반목문에서의 IIFE를 사용하여 클로저를 만든다는것은 결국 클로저변수 j를 리터럴한 숫자로 만들어서 그 숫자를 참조한다는 것일까요?
4달 전
리터럴이라기보다는 안에 함수를 하나 더 넣음으로써 별도의 스코프를 만드는 것입니다. 그 스코프 안에서 값이 고정되게 되는 것이고요. j라는 값은 function(j) 바깥으로 빠져나가지 못합니다. 반면 i라는 값은 for문 바깥으로까지 빠져나갑니다.
4달 전
안녕하세요 제로초님,

var makeClosure = function() {
var name = 'zero';
return function () {
console.log(name);
}
};
var closure = makeClosure(); // function () { console.log(name); }
closure(); // 'zero';

이 코드에 대한 실행 컨택스트를 종이에 그려보며 문뜩 궁금해졌습니다.

전역 컨택스트가 생성, 코드실행 -> makeClosure() ->
makeClosure 실행컨택스트 생성, 코드 실행 -> closure() ->
closure 컨택스트 생성, 코드 실행 이라고 한다면
makeClosure에 대한 실행컨텍스트는 사라지지 않고 호출스택 위에 그대로 남아있는것인가요? 아니면 makeClosure 실행컨택스트가 사라진 후에 closure 실행컨텍스트가 그 위에 있는것인지요?

ps. 유트브 강의 잘 보고 있습니다. 감사합니다!
4달 전
makeClosure은 호출스택에서 나가고 closure()이 올라갑니다. 단, closure()는 makeClosure 컨텍스트에 대한 접근이 가능합니다(렉시컬 스코프에 의해)
4달 전
for (var i = 0; i < 5; i++) {
(function(j) {
$('#target' + j).on('click', function() {
alert(j);
});
})(i);
}
IIFE뿐만 아니라 for의 var을 let으로 바꾸면 for이 실행할때마다 렉시컬 환경이 새로 만들어져서
호이스팅된 전역 변수인 i를 참조하는게 아니라 click이벤트가 발생하였을때 호출되는 함수가 스코프체인을 통해 for샐행마다 생기는 렉시컬 환경(실행 컨텍스트의 변수객체)의 i값을 참조하여 정상적으로 할 수 있습니다!
4달 전
네 그건 let 강좌에서 나와있습니다 ㅎㅎ
6달 전
질문이 하나 있습니다.
button.addEventListener("click",()=>{console.log(this)}) 이런식으로 콜백함수를 썻을 경우 출력값은 window입니다. arrow Function은 상위 context의 this를 계승받는걸로 알고 있습니다. 그럼 addEventListener이라는 메서드가 실행될때 addEventListener 컨텍스트가 생성되고 그안에 this: window가 되어있기때문에 this가 window를 가르키고 있는 게 맞나요?
5달 전
addEventListener는 처음에 실행되고, 나중에 클릭을 하면 익명함수인 () => {} 부분이 실행됩니다.
5달 전
여기서 비동기 함수의 콜백함수인 익명함수의 this는 전역 컨텍스트의 this를 계승받는건가요?
5달 전
네 맞습니다. 화살표함수니까요
7달 전
안녕하세요 https://www.youtube.com/watch?v=h6ysIsRbnKg&list=PLcqDmjxt30Rtbxbh4eJREOVekql_kWVmu&index=60 <- 이 영상에서
6:20 초 경에 function scope를 못벗어난다는 설명이
1. 비동기함수 (setTimeout 함수)안에 있는 function안의 변수든함수든 function이 "실행" 되고나서 값이 결정되므로 function 위에 function 클로저 (j)를 감싸주고 console.log(j) 로 바꿔줌으로서 클로저 function scope에서 못벗어나게 한다.
2. function 클로저 (j)에서의 j는 매개변수로서 실인자이며 0이라는 인자를 받았을 경우 function 클로저 () {var j = 0} 이 되므로 못벗어난다 .
제가 이해한게 맞나요?
7달 전
실행되고 나서 값이 결정되는 게 매개변수 말씀하시는 거군요. 그 스코프를 못 벗어나는 건 맞습니다.
7달 전
👍
8달 전
안녕하세요?

문득 드는 생각이

console.log(word + ' ' + name); // (11)

console.log();에서
log()는 콘솔객체의 메서드,
그러니까 우리가 선언한 함수를 호출하는 게 아니라서
실행 컨텍스트에 포함되지 않는건가요?

감사합니다.
8달 전
함수는 모두 다 포함됩니다.
8달 전
안녕하세요?

밑에 댓글도 쭉 봤는데 헷갈려서 정리해봤습니다. ㅠㅠ


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


var makeClosure = function() {
var name = 'zero';
return function () {
console.log(name);
}
};
var closure = makeClosure(); // function () { console.log(name); } <--㉯
closure(); // 'zero';



㉮ 에서 { makeClosure: Function } 요렇게 표현된 이유가
㉯ 에서 makeClosure(); 요거 하기전에

이미 위에서

var makeClosure = function() {
var name = 'zero';
return function () {
console.log(name);
}
};

이렇게 함수가 대입되어있어서죠?

감사합니다.
8달 전
네 맞습니다.
8달 전
안녕하세요?

counterCloser와 counter

클로저 설명에서 counterCloser맨 뒤에 e가 하나 빠졌습니다.

감사합니다.
8달 전
수정했습니다. 감사합니다.
8달 전
안녕하세요?

함수 컨텍스트 설명에서 궁금한 점이 있습니다.

실행결과를 보면
===============
nero
hello zero
===============

잖아요?


설명중에 "나중에 생긴 wow가 가장 먼저 실행된다"는 문장이 있는데요.



실행은 say가 먼저 실행되고(nero출력), say가 종료되지 않은 상태에서 wow가 실행되고 (hell zero)출력 wow가 종료되고 say가 종료되는 거 아닌가요?


그 밑에 아래와 같은 설명이 있긴 합니다.

"이제 wow 함수 종료 후 wow 컨텍스트가 사라지고, say 함수의 실행이 마무리됩니다."



감사합니다.
8달 전
안녕하세요 제로초님
강의 잘 보고있습니다.
감사합니다.

마지막 이벤트 리스너쪽에서

포문도 블럭이니까
for문의 i가 비공개 변수가 되는거죠?

이 i가 클로저의 매개변수로 들어가니까

이벤트 리스너가 달린외부에서 호출해도
변하는 i값에 따라 작동할 수 있는거죠?
8달 전
var의 경우에는 포문블럭은 영향이 없고요. 함수 블럭만 영향받습니다. let을 사용하는 경우와 비교해 보시면 좋습니다. ECMAScript 카테고리 게시글에 있습니다.
10달 전
안녕하세요 정말 잘 읽었습니다! 감사드려요. 이해가 잘 안 되는 게 있어 여쭤봅니다.

함수 실행시 context 생성된다고 하셨는데 'counter 함수는 호출 시 return을 통해 counterClosure 컨텍스트에 비공개 변수인 count에 접근할 수 있는 scope chain을 반환합니다' 이 말이 잘 이해가 안됩니다~

couter 예제에서 counter가 객체를 return 하고
counterClosure가 그 객체의 key를 통해 increase 등의 함수를 호출하는데
말씀해주신 'counterClousre 컨텍스트'를 어떻게 해석해야할지 잘 모르겠습니다.

감사합니다~
일 년 전
너무 잘 읽었습니다.~~ 퍼고욧
일 년 전
최근에 봤던 클로저 설명중에서 가장 잘 읽히고 재미있었어요! 감사합니다!
일 년 전
너무 어려워요 흑흑 고려대짬바 오졋따
2년 전
안녕하세요 취미로 프로그래밍을 하고 있는 일반인? 입니다. 유튜브 강의도 항상 잘 보고 있습니다. 다름이 아니라 제가 유튜브 또는 구글링을 통해서 모르는 거 찾아가며 간단한 웹 게임을 만들고 있는 중인데요 다음과 같은 코드도 스코프 문제로 해결할 수 있는지 조금봐주시면 안 될까요 며칠째 고민 중인데 개념이 부족해서 잘 모르겠네요.. ㅠ

p.addEventListener('click', function() {
let playerGetCard;
if(stopFlag){
return;
}
for(let i = 0; i < playerPack.length; i++) {
playerPick = playerPack.splice(Math.floor(Math.random() * playerPack.length), 1)[0];
playerPack.length - 1;
playerShuffle.push(playerPick);
playerGetCard = playerShuffle.slice(0,2);
stopFlag = true;
}
console.log(playerGetCard);
});

playerGetCard 변수에 콘솔이 잘 찍히기는 하는데요 저 변수를 이벤트 리스너 바깥에서도 동작하게 하는 방법이 있을까요? 스코프 문제 같기도 하고 아닌 것 같기도 하고 어디 물어볼 사람도 없고 혼자 맨땅에 헤딩하는 처지라 시간 나실 때 답변 주시면 정말 감사하겠습니다. 그럼 하시는 모든 일 모두 굿런 하길 바라며 염치없는 질문 글 좀 남기고 사라집니다...
2년 전
let playerGetCard;를 addEventListener 윗줄로 옮기시면 될 것 같습니다.스코프 문제입니다.
2년 전
그 문제는 저도 시도 해봤습니다. playergetcard 변수를 바깥쪽으로 빼면 이미 선언이 되어있어서인지 언디파인드가 나오더군요.. 그래서 어떻게 해야 할지 고민하다 결국 함수로 따로 만들어서 어찌어찌 해결은 했습니다. 코드가 길어지고 복잡해졌지만 지금 제 능력으로 할 수 있는 최대치네요 아무튼 답변 감사합니다.
2년 전
네 이 문제는 다른 부분 코드와도 연동이되어있어서 그 부분까지 알아야 해결할 수 있을 것 같습니다. 일단 해결이 되었다니 다행이네요.
2년 전
안녕하세요 zerocho님
스코프와 실행 컨텍스트에 대해서 이해할 수 있었습니다 감사합니다
제가 글을 쓴 이유는 제로초님의 웹앱은 SPA(react)로 되어 있는 것 같은데 SEO를 하신 방법을 물어보고 싶어서 입니다.
serverside rendering을 사용하셨는지 BOT 전용 페이지를 사용하셨는지 알 수 있을까요?
2년 전
SSR을 했습니다~
2년 전
안녕하세요 제로조님
제로조님 덕분에 너무 잘 공부하고 있습니다.
한가지 궁금한게 있어서요. 클로저 부분에 2번째 코드 펜스인 '컨텍스트 분석'보시면
makeClosure가 함수표현식인데
variable: [{ makeClosure: Function }, 'closure'] 이부분이
variable: [ 'makeClosure', 'closure' ] 으로 되야되는게 아닌가요?
제가 잘못 이해하고 있는건지요 ㅠ
2년 전
makeClosure() 시의 상황입니다. closure = 이 부분은 아직 실행되지 않았습니다.
2년 전
아래 익명 글쓴이 입니다. 곰곰이 생각해보니... 호이스팅은 변수와 함수선언식이 대상이고 선언 시 변수는 선언만, 함수선언식은 선언과 대입이 동시에 되는 것으로 이해하면 될까요. 그렇다면 왜 이렇게 언어를 구성했는지 궁금하네요.
2년 전
함수선언문입니다. 함수선언문은 문법상 선언과 대입을 분리하기가 매우 힘듭니다. 변수 선언은 var과 = 부분을 분리할 수 있고요.
2년 전
아! 댓글 감사합니다. :) 책 꼭 구매하려고합니다.
2년 전
감사합니다~ 내년에는 자바스크립트 입문서도 나옵니다 ㅎㅎㅎ
2년 전
오직 함수 선언식만 호이스팅이 되는 건가요. 그리고 첫번째 예제코드의 6) 에서 잘 이해가 안되네요. 제가 전체적으로 이해가 덜 됐는지 name의 변수대입이 왜 함수선언식 내 변수대입보다 늦었는지 이해가 잘 안가요 ㅠ_ㅠ
2년 전
세상에 정말 완벽한 설명이다. 정말 대단합니다. 정말 인상깊고 . 정말이지 완벽합니다
2년 전
안녕하세요 제로초님 https://codepen.io/where-code/pen/dQLmdE 혹시 시간되실때 여기에 주석으로 제가달아놓은 내용이 제가 올바르게 이해하고있는건지... 한번 확인해봐주실수있을까요 로그인도 안되고 네이버로그인하려하면 서버오류라고 나와서 익명으로 남깁니다.
2년 전
네네 맞습니다~ 'click' 뒤에 function() {} 부분이 클릭 시 호출되면서 호출스택에 들어가고 함수에 대한 실행 컨텍스트가 생성됩니다.
2년 전
마지막 예제가 잘 이해가 안되서 질문드립니다.
함수가 선언될 때, 스코프가 생성되는 것으로 배웠는데, 그러면 for문이 끝나기 전까지는 함수 선언이 안되기 때문에 for문이 끝나고, i 값이 5가 된 시점에서 alert에 5가 찍힌다는 말씀이신가요?
2년 전
아뇨 함수가 실행될 시점이 중요합니다. 함수가 실행될 때 (클릭 이벤트 발생) i가 이미 5가 되어있습니다.
2년 전
컨텍스트를 이해하니까 스코프에 대해서 알게되고.. 모르던게 확 이해되네요 진짜 쉽게 설명해주셨어요 감사합니다
2년 전
축구에서 조현우가 빛현우로 추앙받고있던데 여긴 빛현영이있네요. 클로져함수를 사용하는법은 알고있었는데 원리를 몰라서 가끔 답답했었는데 여기서 시원하게 알고가네요 감사합니다.
2년 전
var sayYeah = function() {} 이 구문은 sayYeah(); 호출시에 sayYeah 변수에 함수가 대입되는건가요?
2년 전
호출시가 아니라 처음에 var sayYeah = 을 할 때부터 변수에 함수가 대입됩니다. sayYeah()는 그 함수를 호출하는 거고요.
2년 전
은 name 변수나, name 변수가 있는 스코프에 대해 <- name 변수가 있는 이 두번이나 반복됩니다.
2년 전
틀린 문장이 아닙니다. 다시 읽어보세요.
2년 전
앗 죄송합니다 ^^;
2년 전
글 잘 읽었습니다.
글을 읽어본 결과 클로저는
'함수의 컨텍스트에 변수객체(arguments,variable)에없으며 스코프체인으로만 접근할 수 있는 비공개변수를 하나이상 사용할수있는 환경에 놓인 함수'
라고 이해하게 되었는데 맞나요?
2년 전
변수객체 유뮤와는 상관 없고요. 어떠한 스코프나 컨텍스트가 하나 주어져있으면 그 안에 들어 있는 함수는 모두 클로저입니다.
2년 전
그러면
function a(){}
이 a란 함수도 클로저인가요? window 라는 스코프안에 포함되어있으니깐요
2년 전
window 스코프에 대하여 클로저죠.
2년 전
그렇다면 자바스크립트 내에 있는 모든 함수는 window 스코프에 대한 클로저라 볼수 있는건가요?
2년 전
네 맞습니다.
2년 전
친절한 답변 감사합니다 ^^
3년 전
많은 도움이 되는 좋은 글 감사합니다. 추가 궁금한 점이 있어 문의 드립니다..
아래처럼 동일한 이름의 함수와 변수가 사용될 경우
컨텍스트는 변수 선언문에 대한 변수객체를 먼저 생성하고,
그 후에 함수 선언문의 변수객체를 생성하면서 덮어쓰게 되는건가요?
아니면 아래처럼 출력되는 다른 이유가 있는건지 궁금합니다.

console.log(typeof sayWow); // function
function sayWow() {}
var sayWow = "String";
console.log(typeof sayWow); // string
3년 전
네 저런 코드는 안티패턴이라 저도 써본적이 없긴 한데요. 변수 선언이 함수 선언보다 더 위로 호이스팅된다고 들은 것 같긴 합니다.
3년 전
호이스팅에서 전역 컨텍스트에선 variable에 sayWow는 함수 선언식이므로 {say:Function}으로 표현 되는데 왜 맨처음 전역컨테스트 설명해주실때는 wow와 say는 마찬가지로 함수 선언식인데 variable에서 'wow','say' 으로 쓰여질까요?
3년 전
거기 바로 밑에 { wow: Function }, { say: Function } 해두었습니다.
3년 전
좋은 강의 감사합니다 ㅎㅎ 늘 잘보고 응원합니다!
3년 전
감사합니다~~