함수와 블록 스코프

y0ung·2020년 12월 3일
0

스코프(Scope)

스코프(Scope, 유효범위)는 자바스크립트를 포함한 모든 프로그래밍 언어의 기본적인 개념으로 전역스코프와 지역 스코프 두가지가 존재한다.

전역 스코프(Global Scope)

전역 변수를 선언하면 자바스크립트 코드 어디에서든 불러 쓸수 있다. (함수 내부에서도 사용가능하다)

const globalVariable  = 'global';

function foo () {
  console.log(globalVariable) 
}

foo() // 'global'
console.log(globalVariable) // 'global'

전역 변수에서 변수 선언을 하지 않는 것이 권장되는데 두개 혹은 그 이상의 변수들이 같은 이름을 가지게 되어 네이밍 충돌(naming collisions) 발생확률이 있기 때문이다.

const 또는 let 키워드로 같은 이름의 변수를 선언하면 에러가 난다.

let thing = 'something'
let thing = 'something else' // Error, thing has already decleared

만약 var키워드로 변수 선언을 한다면 두번째 변수가 첫번째 변수를 덮어쓴다. 하지만 이런코드는 디버그 하기가 매우 힘드므로 절대로 작성하면 안된다!

var thing = 'something'
var thing = 'something else' // perhaps somewhere totally different in your code
console.log(thing) // 'something else'

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

암묵적 전역

var x = 10; // global

function foo() {
  // 선언하지 않은 식별자
  y = 20;
  console.log(x + y);
}

foo() // 30

y는 선언하지 않은 식별자 이다. 따라서 y = 20 이 실행되면 참조 에러가 발생할 것처럼 보이지만 선언하지 않은 식별자 y는 마치 선언된 변수처럼 동작한다. 이는 선언하지 않은 식별자가 값을 할당하면 전역 객체의 프로퍼티가 되기 때문이다.

자바스크립트 엔진은 y = 20window.y = 20으로 해석하여 프로퍼티를 동적 생성한다. 결국y는 전역 객체의 프로퍼티가 되어 마치 전역 변수처럼 동작한다. 이러한 현상을 암묵적전역(implicit global)이라 한다.

지역 스코프 (Local Scope)

지역변수란 코드 내 특정 구역에서만 사용할 수 있는 변수이다. 지역변수에는 함수 스코프, 블록 스코프 두가지 종류가 있다.

1. 함수 스코프(Functions Scope)

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

function sayHello() {
  const hello = 'Hello , this is Local Scope'
  console.log(hello) 
}
sayHello() //Hello , this is Local Scope
console.log(hello) // Error, hello is not defined

지역변수와 전역 변수 예제

let x = 'global';

function foo(){
  let x = 'local';
  console.log(x);
}

foo() // local
console.log(x) // global

2. 블록 스코프 (Block Scope)

변수를 {} 중괄호 안에 constlet 키워드로 선언했을 때, {} 중괄호 안에서만 이 변수에 접근 할 수 있다.

{
  const hello = "Hello block Scope";
  console.log(hello) // "Hello block Scope"
}

console.log(hello) // Error, hello is not defined

하지만 var키워드를 사용할 경우에는 가능하며, 함수 밖에서 선언된 변수는 코드블록 내에서 선언되었다 할지라도 모두 전역 스코프를 갖게 된다. 따라서 변수 x는 전역 변수이다.

if(true) {
  var x = 5;
}
console.log(x) // 5

블록 스코프는 함수 스코프의 부분집합이다. 함수는 {} 중괄호로 작성되어야 하기 때문인다.

함수 호이스팅과 스코프

hoisting : 끌어 올린다.

  • 자바스크립트 함수는 실행되기 전에 함수 안에 필요한 변수값들을 모두 모아서 유효 범위의 최상단에 선언한다.

    • 자바스크립트 Parser가 함수 실행 전 해당 함수를 한 번 훑는다.
    • 함수 안에 존재하는 변수/ 함수 선언에 대한 정보를 기억하고 있다가 실행시킨다.
    • 유효 범위: 함수 블록{} 안에서 유효
  • 즉, 함수 내에서 아래쪽에 존재하는 내용중 필요한 값들을 끌어올리는 것이다.

    • 실제로 코드가 끌어올려지는 건 아니며, 자바스크립트 Parse 내부적으로 끌어올려서 처리하는 것이다.
    • 실제로 메모리에서는 변화가 없다.

함수 선언문

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

// 1. 작성한 함수 보다 먼저 함수 선언하기
sayHello()

function sayHello(){
  console.log('hello, this is hoisting');
};

---------------------------------------------
  
// 2. 함수를 작성한후에 함수 선언하기
function sayHello(){
  console.log('hello, this is hoisting');
};

sayHello()

함수 표현식

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

sayHello() // Error, sayHello is not defined

const sayHello = function() {
  console.log('hello, this is hoisting');
};

sayHello() // 'hello, this is hoisting'

함수 사용전 미리 선언하는 것이 좋다!

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

함수를 각각 선언했을 때, 함수는 다른 함수의 스코프에 접근할 권한이 없다. 함수 내에서 다른 함수를 불러오더라도 스코프는 사용할 수 없다.

function first(){
  const firstFunctionVariabledp = "I'm part of first"
}

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

second()

secondfirstFunctionVariabledp 에 접근할수 없다.

내부 스코프(Nested Scope)

함수가 다른 함수 안에서 만들어졌고 안쪽 함수는 바깥 함수의 변수에 접근 가능하다. 이러한 것을 렉시컬 스코프(Lexical Scoping) 라고 한다.

하지만 바깥 함수는 안쪽 함수의 변수에 접근 권한이 없다.

function outerFunction () {
	const outer = `I'm the outer function!`;
    
    function innerFunction() {
      const inner = `I'm the inner function!`;
      console.log(outer) // I'm the outer function!
    }
  
  console.log(inner); // Error, inner is not defined
}

스코프가 어떻게 작동하는지 시각화 하기 위해서는 안에서는 바깥이 보이지만 바깥에서는 안이 보이지 않는 유리를 생각 하면 쉽다.

안에서는 밖을 볼수 있지만, 밖에서는 안이 보이지 않는다.

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

클로져(Closures)

크로저는 독립적인 변수(자유변수)를 가리키는 함수이다. 또한, 클로저 안에 정의된 함수는 만들어진 환경(Liexical environment)을 '기억한다'.

자유변수?
클로저에 의해 참조되는 외부함수의 변수

흔히 함수 내에서 함수를 정의하고 사용하면 클로저라고한다. 하지만 대게는 정의한 함수를 리턴하고 사용은 바깥에서 하게된다.

function outerFunction () {
  const outer = 'I see the outer variable!'
  
  function innerFunction() {
    console.log(outer);
  }
  return innerFunction;
}

outerFunction()(); // I see the outer variable!

안쪽 함수가 반환되는 기능을 구현할 때 함수 선언 중 반환 문을 작성함으로써 코드를 줄일수 있다.

function outerFunction () {
  const outer = 'I see the outer variable!'
  
  return function innerFunction() {
    console.log(outer);
  }
}

outerFunction()(); // I see the outer variable!

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

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

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

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

function(x) {
  console.log('A console.log is a side effect!');
}

사이드 이펙트를 제어하기 위해 클로져를 사용하는데 , 이때 Ajax나 timeout과 같은 코드를 망칠 수 있다.

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

makeCake('banana') // made banana a cake!

makeCake함수는 timeout 이라는 사이드 이펙트를 가지고 있다. 함수를 실행했을때 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('Tricks is amazing');

theSecret.saySecretCode() // Tricks is amazing

위의 예제를 보면 saySecretCode는 유일하게 기존 secret함수 밖에서 secretCode를 노출하는 함수이다. 보통 이러한 경우를 privileged function이라 한다.

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

debugging : 코드에서 버그(오류)를 제거하는 것을 의미한다.

크롬과 파이어폭스의 개발자 도구는 현재 스코프에서 볼 수 있는 변수를 디버깅하기 더욱 편리하게 만들어 준다.

사용방법

1. 자바스크립트 코드 내부에 디버깅 하기.

debugger 키워드는 브라우저에서 자바스크립트 실행을 일시정지하고 디버그 할수 있게 도와준다.

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

const makeCakeLater = prepareCake('banana');

개발자 도구에서 확인해보면 사용 가능한 변수들을 보여준다.

prepare의 스코프에 디버깅하기

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

const makeCakeLater = prepareCake('banana');

클로져 스코프에 디버깅 하기

2. 코드에 브레이크 포인트 추가하기

sources탭을 클릭하고 line의 번호를 클릭하면 브레이크 포틴트 추가가 가능하다.

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

요약

  • 함수 내에서 변수를 선언했을 때, 함수 내부에서만 그 변수에 접근이 가능하다. 이변수들은 함수 스코프를 갖는다고 표현할수 있다.
  • 어떠한 함수 안에 내부 함수를 정의한다면 이 함수는 클로져라고 부른다. 클로져는 외부함수로의 접근 권한을 갖는다.

참고

profile
어제보다는 오늘 더 나은

0개의 댓글