반응형

 

 저번 포스팅에서 자바스크립트 언어의 특징을 시작으로 가장 기본적이면서도 중요한 내용들에 대해 알아보았다. (참고, [자바스크립트]자바스크립트 이것만은 꼭 알고가자(Part 1)) 이번 두번 째 자바스크립트 포스팅에서는 자바스크립트에서의 Scope와 Hoisting, Scope Chain, Closure에 대해 정리해보도록 하겠습니다.. 포스팅 내용중에 잘못된 부분이 있거나 개선할 점이 있다면 댓글로 의견 남겨주시면 감사하겠습니다.



1. 자바스크립트에서의 Scope는???

 스코프(Scope)란 범위를 뜻하는 단어로서 자바스크립트에서 변수의 종류에 따라 어느 범위까지 참조가 가능한지에 대한 의미쯤으로 생각하면 되겠다. 크게 전역 스코프(global scope), 지역 스코프(local scope, "함수 스코프"라고도 불림)로 구분된다. 



 1) 전역 스코프(global scope)란 말 그대로 global 영역에 선언된 전역변수가 스크립트 내의 모든 구간에서 사용될 수 있음을 의미한다. 밑의 그림을 보자.

 

 그림을 보면 먼저 글로벌 전역 변수를 선언한 것을 볼 수 있다. var global = 1로 설정한 값은 해당 스크립트의 어떤 영역에서도 사용이 가능하다. 물론 함수안에서도 전역변수를 사용할 수 있는 것을 예제를 통해 알 수 있다. console.log()로 값을 찍어 보면 선언하고 나서 찍었을 때와 함수에서 글로벌 전역변수를 리턴해주는 값 모두 처음 값을 지정해주었던 "1" 이 찍히는 것을 알 수 있다. (참고로 자바스크립트에서는 변수를 선언할 때 타입을 지정하지 않는다. 변수를 사용할 때 무슨 타입이든 var를 사용한다. 또한 변수를 선언할 때 'var'를 입력하지 않으면 전역변수로 인식하기 때문에 전역변수든 지역변수든 모두 'var'를 붙여 일관성을 부여하도록 하자. 밑의 '5)'설명 참고) 





 2) 지역 스코프(local scope, "함수스코프")란 쉽게 말해 해당 함수안에서만 접근가능한 private variable을의미한다. 즉 함수 내에서 var를 사용해 정의된 코드는 지역 스코프에서만 유효하고 해당 함수 내에서 정의된 중첩 또는 자식 함수를 비롯해 해당 함수 내부에서만 사용 가능하다. 

 

 그림을 보면 test()함수안에서 선언된 'local'이라는 지역변수는 함수 외부 {} 밖에서는 그 변수의 유효범위가 끝나버리기 때문에 접근할 수 없는 것을 볼 수 있다. 





 3) 전역변수와 지역변수를 동일한 이름으로 사용하게 되면 어떻게 될까???

 그럼 만약에 전역변수와 지역변수를 동일한 이름으로 사용했을 경우 어떤 문제가 발생할 수 있을까? 자바스크립트에서는 전역 변수 영역에 선언된 변수와 동일한 이름의 지역변수를 사용할 수 있다. 예를 보자.

 

 그림에서 보면 전역변수와 지역변수를 동일한 이름으로 사용을 했을 경우에도 전혀 문제가 되지 않는 것을 볼 수 있다. 보면 test()함수를 실행해 변수의 값을 찍어보면 지역변수의 값(3)이 찍히고 그냥 "global"변수 값을 찍어보면 전역변수의 값인 '1'이 찍히는 것을 확인할 수 있다. 다시말해 전역변수와 지역변수를 동일한 이름으로 사용하여도 각기 다른 변수객체가 만들어 진다는 것을 알 수 있다. 이 부분에 대해서는 밑의 설명들을 참고하길 바란다. (또한 혼란을 막기 위해 이렇게 중첩된 변수 네이밍 사용을 가급적 사용하지 않는 것이 좋다.)





 4) 자바스크립트에는 블록 스코프가 없다. 

 자바스크립트에서는 조건문 ( if() { } )과 반복문( for(){} )은 스코프를 만들지 않으므로 조건문과 반복문 사이에도 변수를 서로 재정의할 수 있다. 

 

 그림에서와 같이 자바스크립트에는 블록 스코프가 없기 때문에 no의 값이 for문에서 바뀌는 것을 확인하였다. 자바스크립트에는 함수, 전역, eval() 스코프만 있음을 명심하자.





 5) 함수 내에서 변수 선언 시 var를 사용해 스코프에서 발생할 수 있는 문제를 피하자.

 위에서 잠깐 설명했다 시피 자바스크립트는 변수를 선언할 때 따로 타입을 지정하지 않고 var를 사용해 선언하게 된다. var 키워드 없이도 변수를 선언할 수 있는데 이는 함수 안에서 선언한다 하더라도 전역 변수의 스코프를 갖게 된다. 

 

 그림과 같이 첫 번째는 test()함수 안에 var를 사용해 지역변수로 선언하고 함수 범위 밖에서 'boo' 지역변수에 접근했을 때는 접근할 수 없지만 두 번째에서는 var 키워드를 사용하지 않고 'boo'를 선언하였기 때문에 전역변수로 인삭하게 되고 test()함수 밖에서도 접근할 수 있게 된다. 따라서 함수 내에서 변수를 선언할 때에는 항상 var를 사용하도록 하자. 그렇지 않으면 스코프 때문에 혼란스러워지는 문제가 발생할 수 있기 때문이다. 





2. 자바스크립트 Hoisting 이란???

 'hoist = 끌어올리다' 라는 뜻으로 자바스크립트에서는 변수 선언과 함수 선언은 해당 유효범위의 가장 최상위로 끌어 올려짐을 의미합니다. 예제를 통해 좀 더 쉽게 살펴보도록 하겠습니다. 

 

 그림을 보게 되면 처음 console.log에서는 undefined를 찍어내는 것을 파악할 수 있습니다. 이와 같은 이유는 자바스크립트 엔진에 의해 아래와 같이 해석되기 때문입니다.

 

 이렇게 'name' 변수의 선언부만 호이스팅 되기 때문에 발생합니다. 자바스크립트의 호이스팅은 함수나 변수의 선언부만 최상위로 끌어올리게 되고 초기화과정은 기존의 위치에서 실행되게 됩니다. 다시 말해 변수의 선언이 초기화나 할당시에 발생하는 것이 아니고, 유효범위의 최상위로 호이스트 되는 것입니다. 유효 범위 안에서 변수에 할당한 값을 활용하고 싶을 때에는 항상 유효범위의 최상위에 선안하고 값을 초기화 해주도록 하는 것이 자바스크립트의 호이스팅기능으로 인해 발생할 수 있는 문제들을 조금이나마 줄여줄 것 입니다.





 3. 자바스크립트의 Scope chain

 자바스크립트는 변수를 찾을 때 스코프의 계층 구조에 기반한 검색 체인을 거슬러 올라가며 추적하게 됩니다. 예제를 통해 좀 더 쉽게 살펴보도록 하겠습니다.

 

 그림에서 보면 console.log를 통해 "scopeChain"을 찍어 내는 모습을 볼 수 있습니다. console.log를 func2 스코프에 쓰여있지만 실제로 자바스크립트 scope chain을 통해 전역 스코프의 scopeChain 변수에 접근에 값을 출력하게 됩니다. func2 함수 스코프 내에 포함되어 있지 않은 scopeChain 값을 찾는 과정은 먼저 func2 함수에서 scopeChain 라는 변수를 찾게 되고, 이 값이 없으면 func2 의 부모 함수인 func1에서 검색을 하게 되고 여기에도 없으면 func1 의 부모 함수가 없기 때문에 전역 스코프에서 값을 찾게 됩니다. 전역 스코프에서 scopeChain을 발견하게 되면 이 값을 func2로 전달하게 되는 것을 자바스크립트의 scopeChain의 핵심 역할 입니다. 만약 전역 스코프에도 해당 내용이 정의되어 있지 않았다면 자바스크립트는 undefinded를 반환하게 됩니다. 



명확한 이해를 위해 예제를 하나 더 살펴보도록 하겠습니다.

 

 그림에서 보듯이 console.log는 func2에서 수행되고 있으면 "number1 + number2 + number3"의 연산을 수행하게 됩니다. 각각의 변수들은 각기 다른 스코프에서 선언이 되었습니다. 자바스크립트는 scope chain을 통해 해당 변수들을 찾게 되고 number3, number2, number1의 순서로 chain을 통해 접근하여 값을 func2 함수로 전달하여 연산을 수행하게 됩니다. 이렇듯 func2 함수에서 참조한 변수는 지역 스코프에 없을 경우 스코프 체인에서 변수를 검색하게 되며 스코프 체인을 검색할 때는 가장 처음 발견한 값을 반한하게 됩니다. (예를 들어 위에 number1, number2 의 명칭을 number3으로 변경해 주게 되면 console.log에는 9가 찍히게 됩니다.)


 이렇듯 스코프 체인을 검색할 때는 가장 처음 발견한 값을 반환하게 되므로 상위 범위에 대해서는 검색을 하지 않고 연산을 수행하여 9가 찍히게 됩니다. 



###스코프 체인은 함수를 실행한 위치가 아닌 정의한 위치에 의해 결정되어 집니다. 이를 가리켜 문법적 스코핑(lexical scoping)이라고도 합니다. 스코프 체인은 함수를 호출하기 전에 이미 만들어지며, 이 덕분에 우리는 클로저(closure)를 만들 수 있게 됩니다. 





 4. 자바스크립트 Closure

 

 Closure는 scope chain과 매우 밀접하게 연관되어 있기 때문에 scope chain의 이해가 우선시 됩니다. 그림에서 보면 sequencer 내부 함수에 ++seq를 반환하고 있는 것을 볼 수 있습니다. 이는 일반적으로 다른 언어에서는 유효범위를 벗어난 쓸 수 없는 변수이지만 자바스크립트에서는 scope chain을 통해서 연결이 가능하게 됩니다. 내부 함수는 자신이 선언된 환경에 대한 연결을 갖게 됩니다. 다시 말해 foo() 함수가 클로져를 갖게 됩니다. 클로져는 두개의 것으로 이루어진 특별한 오브젝트로 첫 번째는 함수, 두 번째는 그 함수가 만들어진 환경을 가지게 됩니다. 그 함수가 만들어진 환경은 함수가 만들어질 때 사용할 수 있었던 변수들로 이루어지고 이 경우에 foo()함수는 sequencer의 내부 함수와 seq 변수를 포함하는 클로져가 됩니다. 





[글을 쓰며]  

 자바스크립트 이것만은 알고가자 (Ver 2)를 포스팅하면서 어떻게 하면 좀 더 다른 누군가가 내 글을 봤을 때 '해당 내용들에 대해 쉽게 이해할 수 있을까'라는 고민을 많이 하게 됬다. 그로 인해 깨달은 것은 내가 그 내용에 대해 완벽히 파악하고 있지 못하다면 쉽게 설명하기 힘들다는 것과 내가 이해하는 내용을 글로 쉽게 옮겨 내는 것 또한 많은 노력이 필요하다는 것이다. 앞으로 글을 쓸 때 앞의 2가지 깨달음을 잊지 않고 포스팅을 해나가도록 하겠다. 



[참조]

자바스크립트를 깨우치다 - 코디 린들리 지음










반응형

+ Recent posts