본문 바로가기
Javascript

JavaScript - Function, Block, Lexical Scope

by Su1993 2020. 9. 15.
반응형

Scope(스코프)

스코프는 '범위'라는 뜻을 가지고 있다. 자바스크립트에서 스코프란 변수에 접근할 수 있는 범위라 볼 수 있다.

자바스크립트에서 스코프는 2가지 타입이 존재한다.

 

  • global Scope(전역 스코프)
  • local Scope(지역 스코프)

 

global Scope(전역 스코프)

전역 스코프는 전역에 선언되어 있어 어느 곳에서든 해당 변수에 접근할 수 있다는 의미이다. 즉, 변수가 모든 함수에 속하지 않고 {} 안에도 들어있지 않다면 이 변수는 전역 변수라 한다. 전역 변수를 선언하면, 함수 내부 또는 자바스크립트 코드 어디에서든 쓸 수 있다.

 

let test = "Hello world"

function a () {
  console.log(test);
}

console.log(test);
// "Hello world"
a();
// "Hello world"

 

다만, 전역 변수를 쓸 경우 아래와 같이 변수들이 중복된 이름을 가지는 네이밍 충돌(naming collisions)이 발생할 확률이 있기 때문에 자주 쓰는 것을 지양해야 한다.

 

let a = "test"
let a = "test test"

// SyntaxError: Identifier 'a' has already been declared

 

Local Scope(지역 스코프)

해당 지역에서만 접근할 수 있어 지역을 벗어난 곳에선 접근할 수 없다는 의미이다. 즉, 코드 내 특정 구역에서만 사용할 수 있는 변수를 지역변수라 한다. 자바스크립트에서는 두 가지 종류의 지역 변수가 있다.

 

  • Function Scope(함수 스코프)
  • Block Scope(블록 스코프)

 

Function Scope(함수 스코프)

자바스크립트에서 함수를 선언할 때마다 새로운 스코프를 생성하게 되는데, 함수 안에 선언한 변수는 함수 안에서만 접근 가능하다. 즉, 함수 밖에서는 함수 안 변수에 접근할 수 없는데 이것을 함수 스코프라 한다.(지역 스코프의 예라고 할 수 있다.)

 

function a () {
  const test = "Hello world"
  console.log(test);
}

a();
// "Hello world"
console.log(test);
// ReferenceError: test is not defined

 

Block Scope(블록 스코프)

블록(block)은 중괄호({})로 둘러쌓인 부분을 블록이라 한다. 변수를 {} 괄호 안에 let 또는 const로 선언했을 때, {} 괄호 안에서만 이 변수에 접근할 수 있다.

 

{
  const a = "Hello world"
  console.log(a);
  // "Hello world"
}

console.log(a)
// ReferenceError: a is not defined

 

화살표 함수를 제외하고 함수를 쓰려면 {}괄호로 작성되기 때문에, 블록 스코프는 함수 스코프의 부분집합이라 할 수 있다.

 

Lexical Scope

함수를 어디서 선언하였는지에 따라 상위 스코프를 결정하는 것을 의미한다. 중요한 것은 함수의 호출이 아니라 함수의 선언에 따라 결정된다는 것이다.

 

let num = 1;

function a() {
  let num = 5;
  b();
}

function b() {
  console.log(num);
}

a(); // 1
b(); // 1

//다른 예시
function c() {
  const outtest = "Hello world outer";
  
  function d() {
    const intest = "Hello world inner"
    console.log(outtest);
  }
  console.log(intest); // intest is not defined
}

 

함수의 호출로 상위 스코프가 결정된 것이 아니라 함수의 선언에 따라 상위 스코프가 결정되었기 때문에 이러한 결과가 나올 수 있다.

만약 함수의 호출로 상위 스코프가 결정되었다면 함수 b의 상위 스코프는 자신을 호출한 함수 a와 전역을 가리키게 되어 함수 a를 호출하여 console.log를 출력했을 때 num의 결과가 5가 출력되었을 것이다.

함수의 호출에 따라 상위 스코프가 정해지는 것을 Dynamic Scope라고도 한다. perl, Bash Shell 등에서 Dynamic Scope를 따른다.

 

Hoisting, Scope

호이스팅이란 함수 안에 있는 선언들을 모두 상위로 끌어올려 해당 함수 유효 범위의 최상단에 선언하는 것을 의미한다.

 

function 키워드와 함께 선언된 함수들은 항상 현재 스코프의 가장 위로 호이스팅 된다.

a();
function a() {
  console.log("Hello world");
}

// 위 아래 코드 결과는 모두 같다.

function a() {
  console.log("Hello world");
}
a();

 

함수 표현식으로 작성된 함수들은 현재 스코프의 가장 위로 호이스팅 되지 않는다.

b(); // ReferenceError: b is not defined
const b = function() {
  console.log("Hello world");
}

 

함수는 항상 사용 전에 미리 선언하는 것이 좋다.

 

그리고 함수는 각자의 스코프에 접근할 수 없다. 함수를 각각 선언했을 때, 함수는 다른 함수의 스코프에 접근할 권한을 갖고 있지 않는다. 함수 내에서 다른 함수를 불러오더라도 스코프는 사용할 수 없다.

function a() {
  const test = "Hello world";
}

function b() {
  a();
  console.log(test); // test is not defined
}

 

Closures(클로져)

함수 안에서 또 다른 함수를 만들 때마다 클로져를 만든 것이다. 안쪽 함수가 클로져이고, 일반적으로 반환시키기 위해 만든다.

반환된 클로져를 이용해 바깥 함수의 변수들을 사용할 수 있다.

 

function a() {
  const out = "Hello world";
  
  function b() {
    console.log(out);
  }
  
  return b;
}

a()();

 

안쪽 함수(b)가 반환되는 기능을 구현할 때 함수 선언 중 반환 문을 작성함으로써 코드의 길이를 조금 더 줄일 수 있다.

function a() {
  const out = "Hello world";
  
  return function b() {
    console.log(out);
  }
}

a()();

 

클로져는 바깥 함수 변수에 접근할 수 있기 때문에 사이드 이펙트(side effects)를 제어하기 위해서 또는 private변수를 만들기 위해서 주로 쓰인다. (사이드 이펙트란 어떤 함수 내에서 자신의 스코프가 아닌 변수들을 제어하는 것)

 

클로져로 사이드 이펙트 제어

함수에서 값을 반환하는 것과는 별도의 무언가를 하는 경우 사이드 이펙트가 발생할 수 있다. timeout, console.log, Ajax요청 등 많은 것들이 사이드 이펙트를 유발할 수 있다.

사이드 이펙트를 제어하기 위해 클로져를 사용할 때, Ajax나 timeout과 같은 코드에 영향이 있는지에 대해 고려해야 한다.

 

예) 친구의 생일케이크를 만드는데 1초가 걸리는데, 1초 후에 made a cake를 로깅하는 함수를 만든다면,

function makeCake() {
  setTimeout(_ => console.log(`Made a cake`), 1000);
}

 

케이크 만드는 함수는 timeout이라는 사이드 이펙트를 가진다. 추가로 케이크의 맛을 고르게 한다면,

function makeCake(flavor) {
  setTimeout(_ => console.log(`made a ${flavor} cake!`), 1000);
}

makeCake('banana');

 

만약 케이크를 바로 만들지 않고 맛을 저장 후 만들고 싶다면, 맛을 저장할 수 있는 prepareCake 함수를 작성하고 prepareCake 내부에 makeCake 클로져를 반환할 수 있다.

 

function prepareCake(flavor) {
  return function() {
    setTimeout(_ => console.log(`made a ${flavor} cake!`), 1000);
  }
}

const makeCakeLater = prepareCake('banana!');

makeCakeLater();

 

이러한 방법이 사이드 이펙트를 줄이기 위해 클로져가 사용되는 방법이다.

 

 

클로져로 private 변수 만들기

private 변수는 외부에서 접근할 수 없고 내부에서만 사용되는 변수를 의미한다. 보통 함수 내부에서 만들어진 변수는 바깥 변수에 접근할 수 없다. 하지만 클로져를 이용해 private 변수에 접근할 수 있다.

 

function a(c) {
  return {
    b() {
      console.log(c);
    }
  }
}

const test = a("Hello world");
test.b()
// "Hello world"

 

위 예에서 b는 유일하게 기존 a함수 밖에서 c를 노출하는 함수이다. 보통 이런 함수를 privileged function이라 한다.

 

 

개발자도구로 스코프 디버깅하기

첫 번째 방법은 자바스크립트 코드 내부에 debugger라는 키워드를 추가하는 것이다. 이 키워드는 브라우저에서 자바스크립트 실행을 일시 정지하고 디버그 할 수 있게 도와준다.

 

prepareCake를 이용한 예제)

function prepareCake(flavor) {
  //adding debugger
  debugger
  return function() {
    setTimeout(_ => console.log(`Made a ${flavor} cake!`), 1000);
  }
}

const makeCakeLater = prepareCake('banana');

 

개발자 도구를 열고 sources 탭으로 가면, 사용 가능한 변수들이 보인다.

 

 

그리고 debugger 키워드를 클로져에 추가할 수도 있다.

function prepareCake(flavor) {
  return function() {
  // adding debugger
  debugger
  setTimeout(_ => console.log(`Made a ${flavor} cake!`), 1000);
  }
}

const makeCakeLater = prepareCake('banana');

 

출처: https://velog.io/@jakeseo_me

 

두 번째 방법은 코드에 브레이크 포인트를 직접 추가하는 것이다. sources탭을 클릭하고 line의 번호를 클릭하면 브레이크 포인트 추가가 가능하다.

 

 

참고

https://velog.io/@jakeseo_me

반응형

댓글