티스토리 뷰

한동안 공부할것도 많고 회사일도 바빠져서 자바스크립트 포스팅을 못했다.

오랜만에 하려니 순서도 기억이 안나고 어디까지했는지도 잘 모르겠어서 클로저에 대한 포스팅을 하고자 쭉쭉 글을 써내려가고있었는데 생각해보니 변수스코프에 대한 얘기를 한적이 없다는걸 깨달았다. 변수스코프는 사실 어떻게보면 굉장히 당연한거고 어렵지않은 개념이지만 클로저를 이해하기위해서는 필수적으로 이해를 해야하는 부분이고, 간단한 소스에서는 이걸 뭘 따로 이해까지해야하나 라고 생각할 수도있지만 앞서말한 클로저 등을 접하게됐을땐 꽤나 복잡해진다. 다음의 소스를 보자.

 

var global = "hello";

 

function getGlobal1(){

  return global;

}

 

function getGlobal2(global){

  return global;

}

 

function getGlobal3(){

  var global = "world";

 

  return global;

}

 

alert(getGlobal1());

alert(getGlobal2("hello world"));

alert(getGlobal3());

 

소스를 실행하기전에 결과를 한번 생각해보자.

변수호출이 발생하면 지역변수 내에서 해당 변수를 찾고 해당 지역에 없으면 상위지역으로 올라가서 변수를 찾는다. 최종적으로 전역 영역까지 올라가게되고 전역에도 해당 변수가 없으면 에러가 발생한다. 이것을 '변수스코프체인' 이라고 한다. 기존에 자바개발자라던가 프로그래밍을 어느정도 공부한사람이라면 매우 당연한거라고 생각할것이고 이걸 굳이 따로 포스팅을 할 이유가 있나 싶을것이다. 하지만 자바스크립트 책을 한번 봐보면 모든 기본서적들은 변수스코프에 대해서 꽤나 많은 지면을 할애한다. 왜일까?

자바에서는 전역변수라고 해봤자 클래스단에 있는 변수들이고 메서드라고해봤자 클래스에서 한단계 아래일뿐이다. 메서드안에 메서드는 못들어간다는 뜻이다.

그러니 메서드에 없으면 한단계만 올라가면 된다. 하지만 자바스크립트의 함수는 메서드와는 다르다. 함수가 변수로 사용이되며 이리저리 옮겨다니고 함수안에 또 함수가있을수있고 그안에 또 함수가 들어갈수있기때문에 사실 이 단순한 개념이 함수가 몇번 옮겨다니다보면 매우 복잡해지고 헷갈리게된다. 변수스코프에 있어 중요한 개념적 단어가 또 하나 있는데 이것도 개념자체는 너무 당연하다고 생각될정도다. 바로 '렉시컬' 특성이다.

렉시컬이란 '변수 스코프는 실행환경이 아니라 소스코드를 따라간다.' 라는 것이다.

코드로 보는게 더 이해가 빠를것이다.

 

var abc = "hello";

 

function func1(){

  var abc = "world";

  alert(func2());

}

 

function func2(){

  return abc;

}

 

func1();

 

func1() 함수를 호출하고 해당 함수는 내부에서 또 func2() 함수를 호출시킨다. 눈치빠른분들이라면 알겠지만 현재 이 func2() 함수 호출 자체도 변수스코프체인이 발동한것이다. 그리고 func2() 함수는 abc를 return 하고있다. func1() 함수 내의 alert()은 무엇을 출력하게될까?

처음 렉시컬에 대해서 공부할때 hello가 나오는게 당연하다고 생각했다. 그리고 실제로도 hello가 출력된다. 너무 당연한걸 실행환경이라는 단어와 렉시컬이라는 단어까지 알아가며 공부하려하니 오히려 더 이해가 안됐다. 실행환경이라 함은 func2()가 '어디에서 호출되었는가' 라는 것이다. 여튼 변수스코프체인은 실행환경이 아니라 렉시컬적 특성을 갖고있다는것은 이런뜻이다.

 

내 배움이 부족한 것인지 변수스코프부분이 정말 쉬운부분인지 사실 변수스코프 자체에 대해서는 이이상 할말이 없다. 이대로 포스팅을 끝내기전에 함수가 지역변수와 파라미터들을 어떻게 관리하는지에 대해서만 설명해보겠다.

자바에서는 오버로딩이라는 기능있다. 메서드명은 동일하게 만들고 파라미터의 갯수와 타입에 따라서 메서드를 구분하는 기능이다. 개발자가 개발중 가장 많은 시간을 할애하는 부분이 명칭짓기라는데 그부분에 있어 대단한 편의성을 제공하는 기능이자 메서드를 호출하는데 있어서도 매우 유연함을 가져다주게된다.

하지만 자바스크립트는 오버로딩을 지원하지않는다. 더군다나 컴파일과정이 없기때문에 컴파일 시점에 파라미터의 타입이나 갯수를 체크하지도않는다. 더더욱이 애초에 변수에 타입을 체크하지않는다.

 

function func(num){

  return num;

}

 

대충 명칭보면 '아 숫자겠구나' 하겠지만 저기에 숫자가 들어있을지 문자열일이 불린타입일지 어떻게 아는가? 타입은커녕 갯수조차 체크하지않기때문에. 그리고 앞선 포스팅에서 배웠듯 함수 자체도 하나의 변수로, 객체의 속성으로 관리되기때문에 오버로딩은 지원하지않는다. 앞서배운것과 지식이 맞물리는것같은 통쾌함이 느껴지지않는가? 하지만 이를 모르는 개발자가 상당히 많다. 포스팅을 하고있는 나도 실무 프로젝트에서 자바스크립트로 오버로딩을 꾀하는 소스를 본적이 있다.

 

function func(num){

  return num;

}

 

function func(num1, num2){

  return num1 + num2;

}

 

전역에 선언한거라면 window객체의 속성으로 들어가기때문에 당연히 오버로딩은 되지않는다. 그렇다면 자바스크립트에서는 하나하나 일일이 이름을 다 다르게 해줘야할까? 그렇지않다. 자바스크립트는 함수내부적으로 파라미터를 관리하는 객체를 '자동생성' 하게된다. 그걸 이용하면 된다.

 

function func(){

  var sum = 0;

  for(var i = 0; i < arguments.length; i++){

    sum += arguments[i];

  }

  return sum;

}

 

alert(func(1, 2, 3, 4, 5, 6, 7, 8, 9));

 

그 객체의 명칭은 'arguments'다. 사실 객체보다는 배열이라는 명칭이 맞다고 생각되지만 배열은 아니라고한다. 말이 좀 웃기긴한데 배열에서 제공하는 push등의 함수는 제공하지않는다. 언어 내부에서 생성하는 객체라 저 객체는 무조건 생성된다. 그러니 파라미터의 갯수를 가지고 다른 로직을 실행해야하는 경우라면 arguments.length 속성을 이용하면된다.

그러나 사실 갯수에따라 완전히 달라지는 로직이라면 다른 게시판에 포스팅한 객체지향설계5대원칙중 SRP(단일책임원칙)를 준수해서 함수를 나누는게 맞지않을까 생각하긴한다.

 

점점 변수스코프와는 관계없는 얘기로 넘어가는데 파라미터의 갯수로 로직을 나눌땐 앞서 말한대로 하면되고 타입으로 로직을 나누는경우도 짧게 언급하고 넘어가겠다.

 

function func(param){

  if(typeof param == "object"){

    return "object";

  }else if(typeof param == "number"){

    return "number";

  }

}

 

alert(func({}));

 

이런식으로 typeof 연산자를 활용하면 된다. alert() 내부는 파라미터로 빈객체를 보낸것이다. 저런 문법에도 익숙해지자. 다만 typeof 연산자는 자바스크립트의 기본 타입만을 판별하기때문에 객체의 생성자까지도 고려한 분기문에는 사용할수가 없다. 얘가 객체인지 아닌지는 판별 가능하지만 생성자가 무엇인지는 알 수 없기때문이다. 이를 대비한 intanceof, toString() 등의 기법이 있는데 여기까지나가는건 정말 여기서 할일이 아닌것같다.

다음 포스팅은 쓰다말았던 클로저로 이어가겠다.

댓글
댓글쓰기 폼