문제를 통해 이해해보는 Javascript 기본 원리

프로그래밍|2016. 7. 29. 15:00

회사에서 자바스크립트 강의를 듣고난 후 많은 것을 알게 되어 이곳에 정리한다.




{

var foo = 1;

console.log(foo);

}

console.log(foo);

위의 코드를 실행하면 어떤 값이 찍힐까? 결과는1

1

1

이렇게 두 번 찍힌다. 블럭안에 있는 foo 변수를 블럭 밖에서 사용하지 못할 것으로 예상되지만 Javascript에서 위의 블럭은 아무런 의미가 없다.










function bar() { 

var foo = 2; 

console.log(foo);

}


bar(); 

console.log(foo);

{ } 블럭은 아무런 의미가 없다고 했는데 function() { } 블럭은 어떨까?

위의 코드를 실행하면 다음과 같이 출력된다.

2

Uncaught ReferenceError: foo is not defined

foo 변수는 함수 블럭 안에 존재하기 때문에 함수 밖에서 foo 변수를 사용하려고 하면 오류가 발생하는 것이다.










var foo = 1; 

console.log(foo); 


var foo = 2; 

console.log(foo) 


console.log(foo)

위의 블럭은 아무런 의미가 없기 때문에 결과는 다음과 같이 출력된다.

1

2

2









function bar() {

foo = 2; 

console.log(foo);


bar(); 

console.log(foo);

위와 같이 bar() 함수 안의 foo 변수 앞에는 var가 붙어 있지 않다.

만약 위의 코드를 실행하면 결과 값은?

2

2

var 예약어를 변수 앞에 정의하지 않으면 window object에 foo라는 propertie가 추가된다.

window.foo 를 실행해보면 2 값이 출려되는 것을 확인할 수 있다.











var foo = 1;

console.log(foo); 

function bar() {

foo = 2; 

var foo; 

console.log(foo); 

bar(); 

console.log(foo);

위의 코드를 실행하면 결과는

1

2

1


위의 결과를 이해하기 위해서는 자바스크립트의 인터프리터가 동작하는 원리를 이해하고 있어야 풀 수 있는 문제이다.

일단 인터프리터가 동작하는 방법은 다음과 같다.

1. 자바스크립트를 파싱할 때 위에서부터 아래까지 쭉 훑어서 선언문들을 탐색한다. 

 - 여기서 선언문은 var 또는 function foo() 들을 말한다. 

2. var로 선언되어 있는 변수들에 대한 공간을 확보 (변수의 값은 모름)

3. function foo() 가 선언되어 있는 것을 감지

4. 스크립트의 첫 줄부터 한 칸씩 실행


자~ 이제 원리를 이해했으니 문제를 다시 한번 풀어보자.

var foo = 1;

console.log(foo); 

function bar() {

foo = 2; 

var foo; 

console.log(foo); 

}

bar();

console.log(foo);


[선언문 감지 처리]

1번째 라인의 foo 변수 공간을 확보

3번째 라인의 bar() 함수 초기화

5번째 라인의 foo 변수 공간을 확보


[실행 처리]

1번째 라인부터 코드를 실행한다.

1번째 라인 실행 시 foo 변수에 1 값이 셋팅

2번째 라인 실행 시 1 값이 콘솔에 출력

8번째 라인의 bar() 함수 실행

4번째 라인 실행 시 foo 변수에 2 값이 셋팅

6번째 라인 실행 시 2 값이 콘솔에 출력

9번째 라인 실행 시 1 값이 콘솔에 출력









(function() {

   console.log("once");

})()

위의 코드를 실행하면 once 가 출력된다. 대게 한번만 호출해야 하는 경우 많이 사용한다고 한다.

자 그럼 다음은 어떻게 동작하게 될까?


var foo = (function() {

   return {

  a : "once"

   }

})()


console.log(foo.a);


결과는 once가 출력된다.

typeof foo 를 해보면 foo 변수의 타입은 object이다.

아마도 foo 변수는 function 타입으로 생각할수도 있지만 코드를 잘보면 끝에 () 가 추가되어 있다.

즉, 인터프리터가 실행 단계에서 해당 함수를 실행한 후 object를 foo 변수에 리턴한 것이다.


만약 () 를 붙여주지 않는 다면 foo는 function 타입이기 때문에 foo.a 와 같이 사용하면 안 되고 foo().a 처럼 사용해야 한다.









var foo = function() {

console.log("once");

foo = function() {

console.log("body");

}

foo();

}


foo();



위의 출력 결과는 다음과 같다.

once

body


단, 다시 한번 foo() 함수를 호출하면 body만 출력되게 된다.

마지막 줄의 foo() 함수가 실행되면 최초 once가 출력되고

그 다음 줄에서 foo 변수에 새로운 function을 대입하게 된다.

그 다음 줄에 foo() 함수를 호출하게 되면 body가 출력이 된다.

단, 여기서 눈여겨 볼 것은 foo 함수는 원래의 function 타입에서 새로운 function 타입으로 변경이 된다.

그렇기 때문에 once는 한번만 호출이 되는 것이다.









(function() {

return function() {

return function() {

console.log(1)

}

}

})()()()



출력 결과는 다음과 같다.

1


()()() 코드로 인해 가장 안쪽에 있는 function이 실행되기 때문이다.








클로저는 외부함수의 변수에 접근할 수 있는 내부 함수를 일컫는다.

클로저를 사용하면서 가장 헷갈리는 부분이 외부 함수가 리턴된 이후에도 내부함수가 외부함수의 변수에 접근하고 있다는 것.

클로저는 외부 함수의 변수 참조를 저장한다. (값 저장 아님)


전역 변수를 사용하지 않고 지역 변수를 함수 외부에서 사용

전역 변수로 인한 문제를 closure를 통해 해결할 수 있음

var add = (function() {

var x = 0

return function() {

return ++x;

}

})()

위는 closure에 대한 예제이다.








다음은 jquery를 이용한 모듈 형태의 구조이다.

var onlyOneClickButton = (function($) {

   

var targetList = []; 

    

    var setTarget = function(targetElementId) {

        targetList.push(targetElementId);

    };

         

    var eventHandle = function() {

};    

     

    return {

setTarget : setTarget,

handle : eventHandle         

    };

}(jQuery));





댓글()