함수와 블록 스코프

EJ·2020년 12월 3일
0

JavaScript 개념

목록 보기
6/13

스코프

자바스크립트에서 스코프는 어떤 변수에 접근할 수 있는지를 정의한다. 일반적으로 2가지의 스코프가 존재한다. 전역 스코프지역 스코프가 존재한다.

Scope = 범위. 즉, 변수에 접근할 수 있는 범위.

전역 스코프(Global Scpoe)

만일 변수가 모든 함수에 속하지 않고 {}괄호 안에 들어있지도 않다면, 그 변수를 전역 변수라고 한다.

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'

전역 변수 사용을 자제하고, 지역 변수 사용지향하자.

지역 스코프(Local Scope)

코드 내 특정 구역에서만 사용할 수 있는 변수를 지역변수라고 한다.

  1. 함수 스코프 지역 변수
  2. 블록 스코프 지역 변수

함수 스코프 지역변수부터 알아보자.

함수 스코프(Function Scope)

함수 내에서 변수를 선언했을 떄, 함수안에서만 이 변수에 접근할 수 있다. 함수 밖으로 나오게 된 이후에는 함수 내부에 있는 변수에 접근할 수 없다.

아래 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

블록 스코프(Block Scope)

변수를 {} 괄호 안에 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!');
}

이러한 두 가지 방법 때문에 함수 호이스팅은 헷갈릴 수 있다. 그리고 사용되어선 안된다. 항상 함수는 사용 전에 미리 선언하자.

함수는 각자의 스코프에 접근할 수 없다

함수를 각각 선언했을 때, 함수는 다른 함수의 스코프에 접근할 권한을 갖고있지 않다.

아래 예제에서, secondfirstFunctionVariable에 접근할 권한이 없다.

function first () {
	const firstFunctionVariable = 'I'm part of first';
}

function second () {
	first();
	console.log(firstFunctionVariable); // Error, firstFunctionVariable is not defined
}

내부 스코프(Nested Scope)

함수가 다른 함수 안에서 만들어졌고 안쪽 함수(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)의 여러 레이어를 생각하면 편하다.

여러 겹의 함수들은 여러 겹의 한 방향 유리를 의미한다.

클로져(Closures)

함수 안에서 또다른 함수를 만들 때마다 사실 클로져를 만든 것이다. 안쪽 함수가 클로져다. 일반적으로 반환시키기 위해서 클로져를 만든다. 반환된 클로져를 이용해 바깥 함수의 변수들을 사용할 수 있다.

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!

클로져는 바깥 함수의 변수에 접근할 수 있기 때문에 주로 두 가지 이유로 쓰인다.

  1. 사이드 이펙트(side effects)를 제어하기 위해서 사용. (사이트 이펙트란 어떤 함수 내에서 자신의 스코프가 아닌 변수들을 제어하는 것을 말한다.)
  2. private 변수를 만들기 위해서 사용.

클로져로 사이트 이펙트 제어하기

함수에서 값을 반환하는 것과는 별도의 무언가를 하는 경우 사이드 이펙트가 발생할 수 있다.
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 변수에 접근할 필요가 있다. 우선 클로져를 이용해 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의 번호를 클릭하면 브레이크 포인트 추가가 가능하다.

브레이크 포인트 추가하여 디버깅하기

요약

스코프와 클로져는 엄청나게 이해하기 어려운 것이 아니다. 한 쪽에서만 보이는 유리를 통해 생각한다면 간단하다.

함수 내에서 변수를 선언했을 때, 함수 내부에서만 그 변수에 접근이 가능하다. 이 변수들은 함수 스코프를 갖는다고 말할 수 있다.

만일 어떠한 함수 안에 내부 함수를 정의한다면, 이 함수는 클로져라고 불릴 수 있다. 클로져는 외부 함수로의 접근 권한을 갖는다.



profile
주니어 프론트엔드 개발자 👼🏻

0개의 댓글