본문 바로가기

Scope n Closures 본문

STUDY/Javascript N Jquery

Scope n Closures

Publee 2019. 2. 26. 16:08

 본 글은 Javascript Scope and Closure by Zell Liew 의 번역 글입니다. 


 스코프(Scope) 란 ? 


Javascript에서  스코프(Scope) 란 어떤 변수들에 접근할 수 있는지를 정의합니다.

스코프엔 두가지 종류가 있는데요, 이는  전역 스코프(global scope) 와  지역 스코프(local scope) 로 나뉩니다.


 전역 스코프(Global Scope) 란? 


변수가  함수 바깥이나  중괄호 ( { } )  바깥 에 선언되었다면,  전역 스코프 에 정의된다고 합니다.


const globalVariable = 'some value'


 전역 변수를 선언하면 코드 모든 곳(함수 포함)에서 해당 변수를 사용할 수 있습니다. 


const hello = 'Hello World!';
function sayHello () {
  console.log(hello);
}
console.log(hello); // 'Hello World!'
sayHello(); // 'Hello Worldr!'


하지만 전역 스코프에 변수를 선언할 수는 있어도, 그러지 않는 것이 좋습니다.

왜냐하면, 두개 이상의 변수의 이름이 충돌하는 경우가 생길 수 있기 때문입니다.

만약 변수를  const  나  let  을 사용하여 선언하였다면, 이름에 충돌이 발생할때마다 에러가 발생합니다.


// Don’t do this!
let thing = 'something';
let thing = 'something else'; // thing has already been declared


만약  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) 


코드의 특정 부분에서만 사용할 수 있는 변수는 지역 스코프에 있다고 할 수 있습니다.

이런 변수를 지역 변수라고 불립니다.


 Javascript에서는 두가지의 지역 변수가 존재합니다. 

이는  함수 스코프(Function Scope) 와  블록 스코프(Block Scope)  입니다.


먼저  함수 스코프(Function Scope) 부터 알아보면,



 함수 스코프(Function Scope) 란? 


함수 내부에서 변수를 선언하면, 그 변수는 선언한 함수 내부에서만 접근할 수 있습니다.

함수 바깥에서는 해당 변수에 접근할 수가 없습니다.


아래의 예제를 보시죠.


function sayHello () {
  const hello = 'Hello World!';
  console.log(hello);
}
sayHello(); // 'Hello World!'
console.log(hello); // hello is not defined


변수  hello 는  sayHello 의 스코프 내에 존재한다는 것을 알 수 있습니다.



 블록 스코프(Block Scope) 란? 


중괄호 ( { } ) 내부에서  const  또는  let 으로 변수를 선언하면, 그 변수들은  중괄호 블록 내부에서만 

접근할 수 있습니다


예제를 보시면

{
  const hello = 'Hello World!';
  console.log(hello);  // 'Hello World!'
}
  console.log(hello);  // 'hello is not defined'

예제에서 볼 수 있듯 변수  hello 는 중괄호 내부의 스코프에만 존재합니다.

함수를 선언할 때는 중괄호를 사용해야 하므로 블록 스코프는 함수 스코프의 서브셋(subset)입니다.




 함수 호이스팅(Function hoisting)과 스코프(Scope) 


함수가  함수 선언식(function declaration) 으로 선언되면, 현재  스코프(Scope) 의 최상단으로 호이스팅됩니다.


예제를 보시면

// This is the same as the one below
sayHello()
function sayHello () {
  console.log('Hello World!');
}
// This is the same as the code above
function sayHello () {
  console.log('Hello World!');
}
sayHello()


두가지 경우엔 같은 결과를 보입니다.


반면 함수가  함수표현식(Function Expression) 으로 선언이 된다면,

함수는 현재  스코프(Scope) 의 최상단으로 호이스팅되지 않습니다.


sayHello() // Error, sayHello is not defined
const sayHello = function () {
  console.log(aFunction)
}

이렇게 두 방식은 행동이 다르기 때문에, 함수 호이스팅은 혼란을 줄 수 있으므로 사용하면 안됩니다.

항상 함수를 호출하기전에 선언해 놓아야 합니다.


함수는 서로의 스코프에 접근할 수 없다.


함수들이 각각 선언되었을 때, 서로의 스코프에는 접근할 수 없습니다.

어떤 함 수가 다른 함수에서 사용되더라도 접근할 수 없습니다.


예제를 보시면

function first () {
  const firstFunctionVariable = 'I’m part of first'
}
function second () {
  first()
  console.log(firstFunctionVariable) // Error, firstFunctionVariable is not defined
}

함수  second 는 변수  firstFunctionVariable 에 접근할 수 없는 것이 확인됩니다.


 네스팅된 스코프(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) 란 ? 


함수 내부에 함수를 작성할때마다, 여러분은 클로저를 생성한 것입니다.

 내부에 작성된 함수가 바로 클로저입니다. 

클로저는 차후에 외부 함수의 변수를 사용할 수 있기 때문에 대게 반환하여 사용합니다.

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) 가 발생합니다.

여러 가지 것들이 사이드 이펙트가 될 수 있는데요, 예를 들어  Ajax  요청이나  timeout 을 생성할때,

또는  console.log 를 선언하는 것도  사이드 이펙트(side effects) 라고 볼 수 있습니다.


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


보통 Ajax나 timeout과 같이 코드 흐름을 방해하는 것들이 신경 쓰일때, 클로저를 활용하여 사이드 이펙트를 제어합니다.


예제를 보면


function playBall() {
  setTimeout(function() {
    console.log('Shooting');
  }, 1000)
}


위의  playBall 함수는 사이드 이펙트가 존재합니다. 바로  timeout 이죠.


이제는 선수가 어떤 슛팅을 할 것인지 선택할 수 있도록 해봅니다.


function playBall(type) {
  setTimeout(function() {
    console.log(type + 'Shooting');
  }, 1000)
}

playBall('heading'); //'headingShooting'

함수를 실행하면, 1초뒤 console이 찍히는걸 볼 수있습니다.


문제가 생겻습니다. 어떤 슈팅을 할지 알자마자 슈팅을 하고싶지 않습니다.

원하는 시점에서 슈팅을 하고 싶다면,

이 문제를 해결하기 위해서, 준비동작을 할 수있는  motion 함수를 작성 할 수 있습니다.

그리고  motion 함수 내부에서는 클로저인  playBall 을 반환합니다.


이제 여러분이 원하는 시점에서 언제든지 반환된 함수를 호출할 수 있고,

이 함수는 호출한 뒤 1초뒤에 슈팅을 하게 만들어집니다.


function motion (type) {
  return function () {
    setTimeout(function() {
      console.log(type + 'Shooting');
    }, 1000)
  }
}

var motionAction  =  motion('heading');

motionAction();  //'headingShooting'


이와 같이 클로저를 활용하여 사이드 이펙트를 줄일 수 있습니다.

여러분이 원할때 내부 클로저를 호출할 수 있는 함수를 만드는 것이죠.


 2. private 변수와 클로저 


함수 내의 변수는 함수 바깥에서 접근할 수 없습니다.

그 변수들은 접근할 수 없기 때문에  Private 변수 라고 불립니다.

하지만 종종 해당 변수들에 접근해야할때가 존재합니다.

이것 또한 클로저를 활용하여 사용할 수 있습니다.

function secret (secretCode) {
  return {
    saySecretCode () {
      console.log(secretCode)
    }
  }
}
var theSecret = secret('Hello World!')
theSecret.saySecretCode()// 'Hello World!'


해당 예제에서  saySecretCode 는 유일하게  secret 함수 바깥에서  secretCode 를 노출하는  함수(클로저) 입니다. 따라서, 이런 함수를  특권 함수(privileged function) 라고 부르기도 합니다.










출처(https://medium.com/@khwsc1/%EB%B2%88%EC%97%AD-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EC%8A%A4%EC%BD%94%ED%94%84%EC%99%80-%ED%81%B4%EB%A1%9C%EC%A0%80-javascript-scope-and-closures-8d402c976d19)

Comments