자바스크립트에서 스코프는 어떤 변수에 접근할 수 있는지를 정의한다. 일반적으로 2가지의 스코프가 존재한다. 전역 스코프
와 지역 스코프
가 존재한다.
Scope = 범위. 즉, 변수에 접근할 수 있는 범위.
만일 변수가 모든 함수에 속하지 않고 {}
괄호 안에 들어있지도 않다면, 그 변수를 전역 변수라고 한다.
Node.js에서의 전역 변수 선언은 약간 다르지만 이번에는 웹 브라우저 내부에서의 자바스크립트만 다룰 것이기 때문에 Node.js에서의 전역변수 선언은 다루지 않을 것이다.
const globalVariable = 'some value'
전역 변수를 선언하면, 자바스크립트 코드 어디에서든 불러 쓸 수 있다. 심지어 함수 내부에서도 사용가능하다.
const hello = 'Hello CSS-Tricks Reader!'
function sayHello() {
console.log(hello);
}
console.log(hello); // 'Hello CSS-Tricks Reader!'
sayHello(); // 'Hello CSS-Tricks Reader!'
전역 변수에서 변수 선언을 할 수 있더라도, 전역 변수 선언을 하지 않는 것이 권장된다. 두개 혹은 그 이상의 변수들이 같은 이름을 가지게 되어 네이밍 충돌(naming collisions)이 발생할 확률이 있기 때문이다.
만일 const
, let
키워드로 같은 이름의 변수를 선언하게 된다면 에러가 발생하게 된다.
// Don't do this!
let thing = 'something'
let thint = 'something else' // Error, thing has already decleared
만일 var
키워드로 변수 선언을 한다면, 두 번째 변수가 첫 번째 변수를 덮어쓰게 된다. 이런 코드를 만들게 되면 디버그 하기가 매우 힘들기 때문에 이런 코드는 작성하지 말자.
// Don't do this!
var thing = 'something'
var thing = 'something else' // perhaps somewhere totally different in your code
console.log(thing) // 'something else'
전역 변수 사용을 자제하고, 지역 변수 사용을 지향하자.
코드 내 특정 구역에서만 사용할 수 있는 변수를 지역변수라고 한다.
함수 스코프 지역변수부터 알아보자.
함수 내에서 변수를 선언했을 떄, 함수안에서만 이 변수에 접근할 수 있다. 함수 밖으로 나오게 된 이후에는 함수 내부에 있는 변수에 접근할 수 없다.
아래 sayHello
함수 안에 들어있는 hello
라는 변수의예제를 살펴보자.
function sayHello() {
const hello = 'Hello CSS-Tricks Reader!';
console.log(hello);
}
sayHello(); // 'Hello CSS-Tricks Reader!'
console.log(hello) // Error, hello is not defined
변수를 {}
괄호 안에 const
, let
키워드로 선언했을 떄, {}
괄호 안에서만 이 변수에 접근할 수 있다.
아래 예제에서 hello
는 {}
괄호 안의 스코프를 갖는다.
{
const hello = 'Hello CSS-Tricks Reader!'
console.log(hello) // 'Hello CSS-Tricks Reader!'
}
console.log(hello) // Error, hello is not defined
븍록 스코프는 함수 스코프의 부분 집합이다. 함수는 {}
괄호로 작성되어야하기 때문이다. 물론 화살표 함수로 즉시 리턴하면 {}
없이 함수를 만들 수도 있다. 그렇지 않은 경우에는 모두 {}
괄효로 작성되어야 한다.
호이스팅 : '끌어올리다"
function
키워드와 함께 선언된 함수들은 항상 현재 스코프의 가장 위로 호이스팅된다.
// 이 코드의 결과는 아래의 코드와 같다.
sayHello();
function sayHello() {
console.log('Hello CSS-Tricks Reader!');
}
// 이 코드의 결과는 위의 코드와 같다.
function sayHello() {
console.log('Hello CSS-Tricks Reader!');
}
sayHello();
함수 표현식으로 작성된 함수들은 현재 스코프의 가장 위로 호이스팅되지 않는다.
sayHello(); // Error, sayHello is not defined
const sayHello = function() {
console.log('Hello CSS-Tricks Reader!');
}
이러한 두 가지 방법 때문에 함수 호이스팅은 헷갈릴 수 있다. 그리고 사용되어선 안된다. 항상 함수는 사용 전에 미리 선언하자.
함수를 각각 선언했을 때, 함수는 다른 함수의 스코프에 접근할 권한을 갖고있지 않다.
아래 예제에서, second
는 firstFunctionVariable
에 접근할 권한이 없다.
function first () {
const firstFunctionVariable = 'I'm part of first';
}
function second () {
first();
console.log(firstFunctionVariable); // Error, firstFunctionVariable is not defined
}
함수가 다른 함수 안에서 만들어졌고 안쪽 함수(inner function)는 바깥 함수(outer function)의 변수에 접근 가능하다. 이것을 어휘적 스코프(lexical scoping)라고 한다.
하지만 바깥 함수에게는 안쪽 함수의 변수에 접근할 권한이 주어지지 않는다.
function() {
const outer = "I'm the outer function!";
function innerFunctio() {
const inner = "I'm the inner function!"
console.log(outer) // I'm the outer function!
}
console.log(inner); // Error, inner is not defined
}
스코프가 어떻게 작동하는지 시각화하기 위해서는 안에서는 바깥이 보이지만, 바깥에서는 안이 보이지 않는 유리(one-way glass)를 생각하면 쉽다.
안에서는 바깥이 보이지만, 바깥에서는 안이 보이지 않는다.
스코프 안에 스코프를 계속 둔다면, 한방향 유리(one-way glass)의 여러 레이어를 생각하면 편하다.
여러 겹의 함수들은 여러 겹의 한 방향 유리를 의미한다.
함수 안에서 또다른 함수를 만들 때마다 사실 클로져를 만든 것이다. 안쪽 함수가 클로져다. 일반적으로 반환시키기 위해서 클로져를 만든다. 반환된 클로져를 이용해 바깥 함수의 변수들을 사용할 수 있다.
function outerFunction() {
const outer = "I see the outer variable!";
function innerFunction() {
console.log(outer);
}
return innerFunction;
}
outerFunction()(); // I see the outer variable!
안쪽 함수(inner function)가 반환되는 기능을 구현할 때 함수 선언 중 반환문을 작성함으로써 코드의 길이를 줄일 수 있다.
function outerFunction() {
const outer = 'I see the outer variable!';
return function innerFunction() {
console.log(outer);
}
}
outerFunction()() // I see the outer variable!
클로져는 바깥 함수의 변수에 접근할 수 있기 때문에 주로 두 가지 이유로 쓰인다.
사이트 이펙트
란 어떤 함수 내에서 자신의 스코프가 아닌 변수들을 제어하는 것을 말한다.)함수에서 값을 반환하는 것과는 별도의 무언가를 하는 경우 사이드 이펙트가 발생할 수 있다.
Ajax 요청, timeout, console.log
문까지 많은 것들이 사이트 이펙트를 유발할 수 있다.
function (x) {
console.log('A console.log is a side effect!');
}
사이드 이펙트를 제어하기 위해 클로져를 사용할 때, 우리가 만든 Ajax, timeout과 같은 코드를 망칠 수 있는 것을 고려해야 한다.
아래의 예제를 통해 살펴보자.
친구의 생일을 위해 생일 케이크를 만들고 싶다고 가정하자. 이 케이크는 만드는데 1초가 걸린다. 그래서 1초 후에 made a cake
를 로깅하는 함수를 만들었다.
여기서는 더 짧고 이해하기 쉬운 코드를 위해 ES6의 화살표 함수를 사용한다.
function makeCake() {
setTimeout(_ => console.log('Made a cake'), 1000;
}
이 케이크 만드는 함수는 timeout이라는 사이드 이펙트를 가지고 있다.
이제 추가로 친구에세 케이크의 맛을 고르게 해보자. 그러기 위해, makeCake
함수에 맛을 추가해야 한다.
function makeCake(flavor) {
setTimeout(_ => console.log(`Made a ${flavor} cake!`), 1000;
}
위 함수를 실행했을 때, 케이크는 그 즉시 1초 후에 만들어진다.
makeCake('banana');
// Made a banana cake!
여기서 문제는 우리가 케이크의 맛을 안 뒤에 바로 만들고 싶지 않다는 것이다. 시기가 적절할 때 만들고 싶다는 것이다.
이 문제를 해결하기 위해, 맛을 저장할 수 있는 prepareCake
함수를 작성할 수 있다. 그 후 prepareCake
내부에 makeCake
클로져를 반환할 수 있다.
아래의 함수를 통해 원할 때마다 반환된 함수를 1초 안에 불러올 수 있게 된다.
function prepareCake(flavor) {
return function() {
setTimeout(_ => console.log(`Made a ${flavor} cake!`), 1000);
}
}
const makeCakeLater = prepareCake('banana!');
// and later in your code
makeCakeLater();
// Made a banana cake!
이게 사이드 이펙트를 줄이기 위해 클로져가 사용되는 방법이다.
private 변수란, 외부에서 접근할 수 없고 내부에서만 사용되는 변수를 말한다.
함수 내부에서 만들어진 변수는 바깥 변수에서 접근할 수 없다. 이러한 이유로 그 변수들은 private 변수라고 불린다.
하지만, 때때로 private 변수에 접근할 필요가 있다. 우선 클로져를 이용해 private 변수에 접근할 수 있다.
function secret (secretCode) {
return {
saySecretCode() {
console.log(secretCode);
}
}
}
const theSecret = secret('CSS Tricks is amazing');
theSecret.saySecretCode();
// 'CSS Tricks is amazing'
위 예제에서 saySecretCode
는 유일하게 기존 secret
함수 밖에서 secretCode
를 노출하는 함수이다. 보통 이런 경우, 이 함수는 privileged function이라고 불린다.
크롬과 파이어폭스의 개발자도구는 현재 스코프에서 볼 수 있는 변수를 디버깅하기 더욱 편리하게 만들어준다. 이 기능을 사용하기 위한 두 가지 방식이 있다.
첫 번째 방법은 자바스크립트 코드 내부에 debugger
라는 키워드를 추가하는 것이다. 이 키워드는 브라우저에서 자바스크립트 실행을 일시정지하고 디버그 할 수 있게 도와준다.
다음은 prepareCake
를 이용한 예제이다.
function prepareCake(flavor) {
// Adding debugger
debugger
return function() {
setTimeout(_ => console.log(`Made a ${flavor} cake!`), 1000);
}
}
const makeCakeLater = prepareCake('banana');
개발자 도구를 열고 소스 탭으로 가면, 사용 가능한 변수들이 보일 것이다.
prepare의 스코프 디버깅하기
debugger
키워드를 클로져에 추가할 수도 있다. 이번엔 스코프 변수들이 어떻게 변하는지 살펴보자
function prepareCake(flavor) {
return function() {
// Adding debugger
debugger
setTimeout(_ => console.log(`Made a ${flavoe} cake!`), 1000);
}
}
const makeCakeLater = prepareCake('banana');
클로져 스코프에서 디버깅하기
디버깅 기능을 사용하는 두 번째 방법은 코드에 브레이크 포인트를 직접 추가하는 것이다.
socurces탭을 클릭하고 line의 번호를 클릭하면 브레이크 포인트 추가가 가능하다.
브레이크 포인트 추가하여 디버깅하기
스코프와 클로져는 엄청나게 이해하기 어려운 것이 아니다. 한 쪽에서만 보이는 유리를 통해 생각한다면 간단하다.
함수 내에서 변수를 선언했을 때, 함수 내부에서만 그 변수에 접근이 가능하다. 이 변수들은 함수 스코프를 갖는다고 말할 수 있다.
만일 어떠한 함수 안에 내부 함수를 정의한다면, 이 함수는 클로져라고 불릴 수 있다. 클로져는 외부 함수로의 접근 권한을 갖는다.