javascript 심화

함수의 범위(scope)

Heoky 2022. 11. 11. 16:53

전역 변수와 지역 변수

let x = 'global';

function ex() {
  let x = 'local';
  x = 'change';
}

ex(); // x를 바꿔본다.
alert(x); // 여전히 'global'

위의 코드를 보면 같은 x여도 ex 함수 바깥의 x는 전역변수고, ex 함수 안의 x는 ex 함수의 지역변수이다.
지역 변수는 함수 안에 들어있는 변수를 의미한다.

 


스코프(Scope)

let x = 'global';

function ex() {
  x = 'change';
}

ex();
alert(x); // 'change'

위의 코드에서는 변수가 선언된 것이 아니기 때문에 전역 변수에 접근이 가능하다. 하지만 아래와 같을 때는 이야기가 다르다.

let x = 'global';

function ex() {
  let x = 'change';
}

ex();
alert(x); // 'change'


함수 안에서 선언된 변수는 해당 변수 안에서만 사용할 수 있다. 이것은 함수 스코프 때문이라고 한다.

 


스코프 체인

let name = 'micro';

function outer() {
  console.log('외부', name);
  function inner() {
    let enemy = 'apple';
    console.log('내부', name);
  }
  inner();
}

outer();
console.log(enemy);

inner 함수에서 name 변수를 찾기 위해 자신의 스코프에서 찾고, 없으면 위로 올라가 outer 함수 스코프 안에서 찾는다.
만약 또 없다면, 위로 올라가 전역 스코프에서 찾는다. 위의 전역 스코프에 name 변수를 찾아 "micro"라는 값을 얻는다.
전역 스코프에도 찾는 변수가 없다면, 찾지 못했다는 에러가 발생한다.

이렇게 꼬리를 물고 계속 범위를 넓히면서 찾는 관계를 스코프 체인이라 한다.

 


렉시컬 스코핑(lexical scoping)

let name = 'micro';
function log() {
  console.log(name);
}

function wrapper() {
  name = 'apple';
  log();
}
wrapper();

 

위의 코드 결과를 예측해보면 wrapper 함수에서 전역 변수 name을 'apple'로 바꿔 log함수를 호출 하고 console에 찍힌 name은
'apple'라는 값을 얻게 된다.

그럼 살짝 바꿔서 코드를 다시 살펴보자.

let name = 'micro';
function log() {
  console.log(name);
}

function wrapper() {
  let name = 'apple';
  log();
}
wrapper();

일단 정답부터 말하자면 'micro'이다. 스코프는 함수를 선언할 때 생긴다.
log 안의 name은 wrapper안의 지역변수가 아니라, 전역변수 name을 가리키고 있다.
이런것을 lexical scoping 이라고 합니다.

함수를 선언하는 순간, 함수 내부의 변수는 자기 스코프로부터 가장 가까운 곳(상위 범위)에 있는 변수를 계속 참조하게 된다.

위의 예시에서 log 함수 안의 name 변수는 선언 시 가장 가까운 전역변수 name을 참조하게 된다.
그래서 wrapper 안에서 log를 호출해도 지역변수 name='apple'를 참조하는 게 아니라 그대로 전역변수
name의 값인 micro가 나오는 것이다.

 


네임스페이스

let obj = {
  x: 'local',
  y: function() {
    alert(this.x);
  }
}

개발을 하다보면 전역변수를 만드는 일은 지양해야한다. 그 이유는 변수가 섞일 수 있기 때문이다.
협업 시 혼자만 개발하는 것이 아니라, 여러 명과 협동도 하고, 다른 사람의 라이브러리를 사용하는 일도 많다.

그런데 전역변수를 사용하다보면, 우연의 일치로 같은 변수명을 사용해 이전의 변수를 덮어쓰는 불상사가 발생할 수 있다.
(특히 $나 _같은 유니크한 변수들은 경쟁이 치열)

간단한 해결 방법은 전역 변수 대신 한 번 함수 안에 넣어 지역변수로 만드는 것이다.
또는 위와 같이 객체 안의 속성으로 만들 수도 있다.

위 처럼 하면 obj.x 또는 obj.y() 이렇게 접근해야 하기 때문에 다른 사람과 섞일 염려가 없다.
전역변수를 하나로 최소화해서 변수가 겹칠 우려도 최소화하는 것이다. 이를 네임스페이스를 만든다고 한다.

 


공개 변수와 비공개 변수

위에서 객체 안의 속성으로 네임스페이스를 만들면 누군가 고의적으로 x와 y를 변경할 수 있다.
이를 방지하기 위해 아래와 같은 방법으로 하면된다.

let another = function () {
  let x = 'local';
  function y() {
    alert(x);
  }
  return { y: y };
}
let newScope = another();

another(); 하는 순간 return에 의해 { y: function ( ) { alert(x) } };newScope에 저장된다.
이제 newScope 이라는 네임스페이스를 통해서 y에 접근할 수 있다. x는 접근할 수가 없다.

위의 코드처럼 return을 통해 공개할 변수(y)만 공개하고 비공개할 변수(x)는 비공개하는 방법을 취할 수 있다.
즉, return 하는 변수는 공개변수고, 다른 것은 비공개 변수이다.

아래의 코드는 위의 코드를 간략하게 바꾼 것이다.

let newScope = (function () {
  let x = 'local';
  return {
    y: function() {
      alert(x);
    }
  };
})();

이와 같이 newScope에 바로 집어 넣을 수 있는데, 코드를 보면

(function() {})()

이를 IIFE(즉시 호출 함수 표현식)이라고 한다. 모듈 패턴이라고도 한다. 함수를 선언하자마자 바로 실행시켜버린다.

이 구문은 라이브러리를 만들 때 기본이된다. 많은 라이브러리가 이 구문을 활용하고있다.
공개 변수가 없는 자바스크립트에 비공개 변수 기능을 만들어주기 때문이다. 이 패턴은 꼭 기억하고 있어야 한다.