웹 취약점 공격 방법인 XSS, CSRF에 대하여 간단하게 알아보기
Javascript #7 자바스크립트의 스코프와 호이스팅에 대해 알아봅니다.
2019-02-28
Explanation
이런저런 일들로 정신없는 하루하루.. 오늘은 자바스크립트의 스코프, 호이스팅에 대해 가벼운 마음으로 간단하게 적어 보려합니다. 어떻게 생각해보면 자바스크립트의 가장 기본이 되는 부분인데, 자바스크립트를 그렇게 많이 사용하면서도 제대로 알지 못하고 있었던거 같아요 흑…
그리하여 우선은 이 글을 통해 알고 있는 선에서 간단하게 적어보고 그 다음엔 심화편으로 다시 깊이 있게 알아보려 합니다.
저의 개인적인 생각이 들어가 있기 때문에 이 글을 너무 믿으시면 안됩니다, 잘못된 부분이 있을 수 있어요.
그리고 이 글은 NodeJS가 아닌 브라우저를 기준으로 작성되었습니다.
코드의 유효범위를 구분 짓는 단위라고 생각할 수 있을 거 같아요.
1 2 3 4 5 6 |
//하나의 스크립트 태그 영역으로 보자면 <script> // <script> 태그로 시작해서 // (여기) // </script> 태그로 끝나는 사이를 전역 스코프라고 부릅니다. </script> |
그리고 ‘ { ‘ 요걸로 시작해서 ‘ } ‘ 요걸로 끝나는 저 구간의 지역 스코프라고 부르는데요.
1 2 3 |
function() { // 이렇게 함수 에서 사용되는 ' { ' ... ' } ' 요 구간을 함수 스코프라고 부르고요 } |
1 2 3 |
if(true) { // 이렇게 함수 외의 ' { ' ... ' } ' 요 구간을 블록 스코프라고 부른답니다. } |
요기서 이야기하는 함수 스코프와 블록 스코프와 함께 생각해두면 좋은 것이 바로 ES6에 추가된 const와 let인데요. ES6 이전에 변수를 선언하던 var는 함수 레벨 스코프의 변수 선언이고, const와 let은 블록 레벨 스코프의 변수, 상수 선언입니다. 이게 무슨말인 즉슨,
1 2 3 4 5 6 7 8 9 |
if(true) { var aaa = 1; } console.log(aaa); //1 if(true) { const bbb = 1; } console.log(bbb); // ReferenceError: bbb is not defined |
이렇게 var 는 함수 레벨 스코프의 변수 선언이기 때문에 블록 스코프 정도는 가뿐히 뛰어 넘지만, const나 let은 블록 레벨 스코프의 변수(상수) 선언이기 때문에 해당 스코프를 벗어나면 찾을 수 없습니다.
그리고 하위 스코프 영역의 요소에서는 상위 스코프의 변수나 함수를 호출할 수 있지만 상위 스코프 영역에서는 하위 스코프의 값을 호출할 수 없습니다. 역시 그말인 즉슨,
1 2 3 4 5 6 7 8 |
(function() { // 1번 함수 const aaa = 1; (function() { // 2번 함수 const bbb = 2; console.log(aaa) // 1 })(); console.log(bbb); // ReferenceError: bbb is not defined })(); |
요로코롬 부모 함수(1번 함수)에서 선언된 aaa 변수는 자식 함수(2번 함수)에서 호출할 수 있지만, 자식 함수에서 선언한 bbb는 부모 함수에서 호출할 수 없습니다.
스코프를 마무리하며, 자바스크립트를 잘 알지도 못하던 시절부터 귀에 못이 박히게 들은 이야기가 ‘전역을 최대한 쓰지마라..’ 였는데, 왜 그러냐면요. 일반적으로 우린 많은 라이브러리나 플러그인들을 사용하고 또는 역할에 맡게 스크립트 파일을 분리해서 개발하고 선언하잖아요? 가령 아래와 같이
1 2 3 4 5 6 7 8 9 10 11 12 |
<script> let aaa = 1; </script> <script> let bbb = 2; </script> <script> console.log(aaa); // 1; console.log(bbb); // 2; </script> |
요런식으로 스크립트 태그로 또는 파일로 분리되어 있어도 모두 하나의 전역을 가지고 있거든요. 그렇다는 건, 다양한 스크립트들이 하나의 무대에 모이게 되고 전역을 많이 사용하다보면 예기치 않게 다른 스크립트로 인해 재정의 되거나 중복 오류를 뿜게 되기 때문입니다.
자바스크립트는 인터프리터 언어로 컴퓨터가 순차적으로 코드를 한줄 한줄 해석합니다. 그런데 자바스크립트에서는 조금 예상과는 다른 결과를 얻을때가 있는데요. 예를 들면 아래와 같습니다.
1 2 3 4 5 |
console.log(fnc()); // 'abc' function fnc() { return 'abc'; } |
이렇듯 fnc라는 함수를 선언하기 전에 호출하였는데, 이상하게도 오류가 뜨지 않고 정상적으로 호출이 되는 것을 볼 수 있습니다.
요로한 상황?을 호이스팅이라고 하는데요.
* 요기서부터 약간 개인적인 이해 방법이 섞여 있어서 사실과 다를 수 있습니다.
자바스크립트는 한줄씩 코드를 읽어나가기 전에 스코프 영역 전체를 한번 쭉 훑어보고 변수나 함수의 선언을 먼저 메모리에 올려놓습니다. 그리고 나서 순서대로 코드를 읽어나가기 시작하죠. 가령 예를 들자면 아래와 같은 코드가 있다면,
1 2 3 |
var a = 'a'; var b = 'b'; var c = 'c'; |
아주 간단한 코드죠? 변수 a, b, c,에게 문자열 ‘a’, ‘b’, ‘c’ 각각 초기화합니다. 실제로 저 코드는 변하지 않지만 컴퓨터가 이해하는 것은, 앞서 이야기한대로 우선 스코프 안에서 변수의 선언을 메모리에 먼저 정의하고 시작합니다. 그걸 코드로 구현하면 아래와 같겠죠?
1 2 3 4 5 6 7 |
var a; var b; var c; a = 'a'; b = 'b'; c = 'c'; |
지금의 이 코드는 너무 간단해서 사실상 느낄 수 있는 차이가 없지만, 대충 이렇다는 것을 이해하고 다음의 코드를 보면
1 2 3 4 5 6 7 |
var a = 'a'; function abc() { console.log(a); // 첫번째 var a = 'b'; console.log(a); // 두번째 } abc(); |
결과를 출력하면 첫번째 console.log(a)는 ‘a’를 출력하고 두번째 console.log(a)는 ‘b’를 출력할 것 같지만, 실제로는 첫번째는 undefined 두번째는 ‘b’가 출력 됩니다. 앞서 이야기한 컴퓨터가 이해하는 순서로 위 코드를 다시 작성해 보면,
1 2 3 4 5 6 7 8 9 |
var a; a = 'a'; function abc() { var a; console.log(a); // undefined a = 'b'; console.log(a); // 'b' } abc(); |
그리고 맨 처음에 조금 예상과 다른 결과라 이야기했던 함수도 역시 위 변수의 선언이 먼저 메모리에 저장되는 것처럼 함수 선언 역시 먼저 메모리에 저장되기 때문에 코드상 함수 호출이 먼저 위치하더라도 오류가 나타나지 않고 정상적으로 동작하는 것 입니다.
하지만 여기서 약간 헷갈릴 수 도 있는 부분은 함수의 선언을 변수에 했을때인데요,
1 2 3 4 5 |
console.log(fnc()); var fnc = function() { return 'fnc'; } |
이때는 변수에 초기화 되지 않고, 변수의 선언이 호이스팅 되는 것과 같이 동작합니다.
1 2 3 4 5 6 |
var fnc; console.log(fnc()); // TypeError: fnc is not a function fnc = function() { return 'fnc'; }; |
원래 생각은 자바스크립트의 프로토타입까지 적는 것 이었지만… 시간이 너무 늦은 관계로 프로토타입은 다음(언젠가..) 시간에…