반응형


자바스크립트의 변수

변수는 프로그래밍언어를 배울때 가장먼저 접하는 개념입니다. 언어가 어떤 형태의 데이터를 선언하고 다룰것인지를 결정하는 부분이라 실제 언어의 특성들을 이해하기 전에 가장 먼저 접할수 밖에 없습니다.

보통은 언어는 특정 데이터 타입을 가지고 있습니다. 예를들면 int, string, date등 해당 데이터타입으로 데이터를 설정하는 방법이 있지만, 자바스크립트의 경우에는 느슨한 데이터 타입(loosley data type)이기에 모든 데이터 타입을 var 로 지정을 할수 있습니다.
문자열, 숫자, 객체, 심지어 함수까지도 변수로 지정하고 있습니다. 그래서 자바스크립트의 변수를 이야기 하다보면 자바스크립트의 거의 모든 부분을 다루게 됩니다. 

이처럼 변수가 자바스크립트에서 단순히 값을 설정하고 사용하는 개념에 그치지 않고 참조변수, 스코프, 클로저, 컨택스트, this등 자바스크립트를 제대로 이해하기 위한 핵심적인 개념을 담고 있기에 반드시 이해하고 넘어가야 하는 부분입니다.
위에서 나열한 내용을 제외하고는 프로토타입 정도가 어려운 개념으로 남을듯합니다.

그럼 변수와 관련된 내용을 하나하나 알아가보도록 하겠습니다.


[변수 선언]
자바스크립트에서 변수는 선언하는 위치를 반드시 신경을 써줘야 합니다. 그 변수의 위치에 따라 변수가 의미하는 바가 달라지기때문입니다.


[전역변수 or 지역변수]
변수를 처음 접하면 가장 먼저 접하게 되는 부분이 바로 전역변수냐 지역변수냐의 문제입니다. 즉, 코드의 어떤 부분에서든 접근하게 해줄것이냐 아니면 필요한 부분에서만 사용할것인지를 결정하는 부분입니다.
프로그램 상에서 변수의 활용범위는 전역변수 > 지역변수 이지만 변수의 영향력은 전역변수 < 지역변수 입니다. 전역변수보다 지역변수가 우선순위를 가지게 됩니다.

지역변수 함수내부에서 선언된 변수로 함수 내부에서만 사용하겠다는 의미의 변수입니다. 단순히 함수 내부에서만 사용하는 변수이다 보니 프로그램의 전체 로직에 영향을 주지 않습니다. 해당 함수의 로직에만 영향을 줍니다.
물론 전역으로 선언된 변수에 함수의 리턴방식으로 지역 변수를 전역 변수로 사용하게 한다든지, 또는 setTimeout 내부에 선언된 함수는 전역에서 실행되는등 의도치 않게 또는 의도적으로 지역변수가 전역화 되는 경우도 있습니다.

보통은 전역변수를 만들때는 함수내부가 아닌 전역에서 var를 통한 변수를 선언하여 전역변수를 정의 하지만, 대부분 프로그래머의 실수로 인해 var 를 사용하지 않고 그냥 변수를 선언해 변수가 전역화가 되는 경우도 있습니다. 
var를 사용하지 않고 선언한 변수가 왜 전역변수가 되는지는 아래의 변수 생성과정을 통해 더욱 자세히 설명하겠습니다.

var globalValue = “i’m global”;
function func(){
    globalValueTwo = “i’m global. too”;
}

변수의 선언에 있어서 유념해야 하는 부분은 '변수의 선언 = 메모리' 라는 개념입니다. 그냥 전역변수를 무분별하게 선언하고 사용하면 결국 메모리 누수가 발생하게 되어 어플리케이션의 성능에 문제가 될수 있습니다. 물론 브라우저들의 성능이 무척이나 좋아지고 브라우저가 스스로 쓰지 않는 변수들을 이래저래 잘 정리를 해주지만 그래도 쓸데없는 변수의 선언은 피하는것이 좋습니다.
그리고 무엇보다 중요한 부분은 전역번수를 함수를 여기저기서 접근하다 보면 의도치 않게 값이 변경이 되어 원하는 결과를 얻지 못하는 경우가 있습니다. 
항상 전역스코프는 깔끔하게 유지해두는것이 좋습니다.
이런 문제를 해결하기 위해서 ES6부터는 const, let등 추가적으로 변수를 선언하는 방법들이 소개되고 있지만 아무튼 전역변수의 사용은 항상 조심해야 하는 부분입니다.


[유효범위(scope)]
변수가 전역과 지역이라는 특정범위에서 유효한 영향 가진다는 것을 알게 되었습니다. 앞으로 변수의 유효한 범위를 스코프라고 하겠습니다. 그럼 스코프에 대한 이야기로 넘어가보겠습니다.

스코프의 핵심은 함수단위의 유효범위 입니다.

이 한가지 사실이 모든 것을 설명합니다. 즉, 해당 함수에 정의된 함수는 해당 함수내에서만 유효한 값을 행사한다는 의미입니다.

그런데 아래와 같은 코드가 헷갈리는 부분입니다.

function callYOU(){
     var myname = “james”;
     callAdam();
}

function callAdam(){
     return myname;
}
callYOU();

어떤 결과가 리턴이 될까요? james 라는 결과를 예상할수 있지만 이 경우에는 에러가 발생합니다.

우리가 기대할때는 callAdam을 호출하는 시점에 분명 myname변수가 함수내에 선언이 되어있고 그 다음에 호출하는 callAdam이 myname 변수에 접근을 할수 있을 것이라고 기대를 합니다.
그러나 에러가 발생합니다. 
이유는 변수는 함수가 호출하는 시점이 아닌 함수가 정의 되는 시점에 생성이 되기 때문입니다. callAdam이 정의 되는 시점에 myname이라는 변수는 callYOU 함수 안에서만 존재합니다. 당연히 지역변수입니다. 즉, 외부 함수에서는 접근이 안된다는 말입니다.
지금 당장 함수의 정의 시점에 따라 접근가능한 변수가 달라진다는 말에 의아해하실수도 있는데 앞으로 다룰 실행컨택스트의 변수객체와 this의 개념을 이해하시면 자연스럽게 이해가 되실내용입니다. 지금은 유효범위는 함수단위로 정해진다라고만 이해하시고 넘어가겠습니다.


[호이스팅]
자바스크립트에는 호이스팅이라는 개념이 있습니다. 이는 변수의 선언과 변수의 할당을 구분하겠다는 개념입니다. 즉, 함수내의 선언된 어떤 위치의 변수든 함수의 최상단으로 끌어올려집니다. 그리고는 undefined로 임의의 값을 할당합니다. 그리고 나중에 함수가 실행이 되면서 그 변수에 해당하는 값을 할당합니다.
예를들어 보겠습니다.

function hoistingTest(){
     console.log(’greeting : ’ + hi);
     var hi = “hello”;
     console.log(’greeting : ’ + hi);
}
hoistingTest()

위 코드는 호이스팅을 설명하는 일반적인 코드입니다. 위의 코드는 분명히 에러가 나야 하는 상황입니다. hi라는 변수가 선언이 되지도 않았는데 hi변수를 이미 호출해서 사용했습니다. 그런데 에러는 나지 않습니다.
즉 변수의 선언이 먼저되어 있음을 알수 있습니다. 할당은 그 이후에 해당 코드가 실행이 되면서 이뤄졌습니다.
이 개념이 바로 자바스크립트에서의 호이스팅의 개념입니다.


[실행 컨택스트]
이제 변수는 물론 자바스크립트를 이해하는 핵심이 되는 실행컨택스트에 대한 내용입니다.
자바스크립트에서의 코드는 크게 세가지로 분류가 됩니다. global 코드, function 코드, eval 코드. 그리고 이 모든 코드는 실행컨택스트에 들어와 실행이 됩니다. 즉 자바스크립트에서 실행되는 모든 것들을 관리하는 부분이 바로 실행컨택스트부분입니다.

글로벌 컨택스트의 경우에는 오직 하나만 존재하는 반면에 function, eval 컨택스트는 하나의 프로그램 내에서 여러개가 존재할수 있습니다.

만약에 함수가 하나 호출이 되면 실행의 흐름이 함수 컨택스트로 들어가게되고 eval이 실행이 되면 이도 eval 컨택스트로 들어가서 실행이 되게 됩니다. 기본적으로 하나의 함수는 무한대의 컨택트스를 생성합니다. 아래의 예를 보겠습니다.

function foo(bar){}
foo(10)
foo(20)
foo(30)

함수가 각기 다른 인자를 가지고 세번 호출이 되었습니다. 이경우에는 서로다른 실행컨택스트가 생성이 됩니다.  즉 동일한 함수의 실행이라도 이미존재하는 컨택스트를 재활용하는 것이 아닌 새로운 컨택스트를 계속 생성하게 됩니다.

그럼 실행 컨택스트의 내부가 어떤 식으로 동작을 하는지 알아 보겠습니다.
실행컨택스트는 실행컨택스트의 스택이라는 영역에 저장이 됩니다. 실행컨택스트의 스택은 나중에 들어온 컨택스트가 스택의 최상단에 위치하게 됩니다. 즉 현재 실행이 되고 있는 컨택스트를 의미하고 이를 callee라고 부릅니다. 실행 컨택스트 내에서 다른 컨택스트를 실행시키기 위해서 필요한게 caller이고 caller를 통해 다른 컨택스트를 실행하게 됩니다.

예를들면 다음과 같습니다. 

caller가 어떠한 context를 실행하게 되면 기존에 caller가 동작시키고 있던 실행의 흐름이 새롭게 동작하는 context, 즉 callee로 변경이 됩니다. 이때 callee는 실행컨택스트의 최상단에 위치하게 되고 Active Context라는 이름을 가지게 됩니다.
그리고 이 callee, 즉 active context의 동작이 끝이 나면 실행흐름을 다시 caller에게 전달하여 다른 context쪽으로 실행의 흐름을 변경하는 작업이 반복되게 됩니다. 그리고 동작을 마친 예전callee는 간단하게 return을 하거나 exception과 함께 exit을 하게 됩니다.



프로그램이 일단 실행이 되면 모두 글로벌 실행 컨택스트(global exceution context)로 들어오게 됩니다. 그리고 이 글로벌 컨택스트는 실행 컨택스트 스택의 제일 아래에 존재하며 이는 최초로 생성된 컨택스트를 의미하기도 합니다.
그후에 글로벌 코드들이 몇몇 초기화 작업을 진행하며 필요한 객체나 함수들을 생성을 합니다. 이렇게 글로벌 컨택스트가 실행이되는 동안 새로운 함수들이 생성이되고 실행이 되면서 스택내로 들어와 글로벌 컨택스트 위로 차곡차곡 쌓이게 됩니다. 일단 초기화가 끝이 나면 runtime 시스템은 사용자의 클릭이나 글로벌하게 발생하는 함수 실행같은 이벤트가 발생하기를 기다립니다. 
그리고 이벤트가 발생이 하게 되면서 실행컨택스트 내의 컨택스트들이 하나하나동작을 하게 됩니다.

위와 같은 일련의 과정을 그림으로 나타내면 아래와 같습니다.


이와 같은 형태가 EXMAScript, 즉 우리가 사용하는 자바스크립트에서 코드의 실행을 관리하는 형태입니다.


[실행컨택스트]
실행 컨택스트의 동작방식을 이해했으니 내부 구조에 대해 알아보겠습니다. 
실행컨택스트는 일단 크게 세가지의 형태로 필요한 정보를 담고 있습니다. 아래는 실행컨택스트의 구조입니다.


위와 같은 형태로 실행 컨택스트는 정보를 담고 있습니다. 그럼 세가지 요소를 하나하나 살펴보겠습니다.

[변수 객체(Variable object)]
변수객체는 실행컨택스트와 연관된 데이터의 정보를 담고 있는 객체로 대부분 변수나 함수를 선언하면 이곳에 저장이 됩니다. 쉽게 표현을 하면 var를 통해 변수를 선언하면 이곳에 프로퍼티로 저장이 된다는 말입니다. 명심할부분은 var를 통한 선언입니다.

실행컨택스트 = {
     변수 객체 : {}
}

이러한 형태로 이해를 하면 됩니다. 그래서 우리가 변수나 함수를 생성을 한다는 의미는 변수객체에 프로퍼티와 값을 추가 한다는 의미와 동일합니다. 예를들면

var global = "hi";

function func(){
     var local = "yo"
}

(function addMe(){})()

func()

라고 생성을 하게 되면 이는 두개의 변수 객체를 생성하게 됩니다. 기본적으로 생성이 되는 변수 객체와 함수호출에 따른 func 실행객체내의 변수 객체입니다.

전역 변수객체(글로벌) = {
     global : "hi",
     func : <reference to function>
}

func함수 변수객체 = {
     local: "yo"
}

여기서 주의 깊게 봐야하는 부분이 addMe 함수입니다. 이런 function expression 형태로 정의 된 함수는 변수객체에 따로 저장이 되지 않습니다. addMe 함수에 우리가 접근하려고 시도하면 ReferenceError를 전달하게 됩니다.

그럼 전역컨택스트의 전역변수 객체와 함수 컨택스트의 변수 객체는 어떤 차이를 가지고 있을까요. 좀더 알아 보겠습니다. 

전역변수객체의 경우 앞에서도 이야기 했지만 일단 프로그램이 시작이 되면 초기 세팅을 시작합니다. 우리가 사용하는 Math, String 또는 window 객체에 대한 내용들을 미리 지정해두고 있습니다. 그리고 이는 프로그램의 어떠한 곳에서도 접근이 가능합니다. 
이 전역객체의 경우 이름을 통해 접근하는 것이 가능하기때문에 바로 해당 프로퍼티의 이름으로 바로 접근을 하는 것이고 전역컨택스트 상에서는 this나 window 같은 프로퍼티를 통해서 접근하는 것도 가능합니다.

반대로 함수 객체의 경우에는 직접적으로 변수객체에 접근하는 것이 불가능합니다. 그래서 그 변수객체의 내용을 그대로 가지고있는 Activation Object라는 것이 존재하여 이를 통해 우리는 변수들을 사용하게 됩니다. 일단 함수가 caller에 의해서 실행이 되면 activation object라고 불리는 특별한 객체가 생성이 되는데 이는 함수내에서 사용하는 일반적인 파라메터에 대한 정의는 물론이고 argument라는 특별한 객체또한 생성하게 됩니다.
그리고 이렇게 생성된 Activation Object를 우리는 함수의 변수 객체로 사용을 하게 됩니다.

function foo(x,y){
     var z = 30;
     function bar() {}
     (function baz(){});
}
foo(10,20)

위코드를 보시면 우리가 이미 전역 변수 객체를 다룰때 사용한 코드와 동일한 코드이지만 모든 코드를  foo라는 함수로 한번 감싼형태입니다. foo(10,20) 함수가 실행이 되면서 다음과 같은 형태의 Activation Object를 가지게 됩니다.


Activation Object가 가지는 프로포티는
- 함수 내부에 정의 한 변수
- callee : 현재 실행되고있는 함수에 대한 참조
- length :  전달된 인자 갯수
- properties-indexes
- argument : 우리가 정의한 파라멘터를 인덱스와 함께 배열형태로 정의하고 있습니다. 


그럼 여기서 위에서 간단하게 설명을 했던 변수의 호이스팅의 개념이 조금더 자세한 메커니즘과 함께 설명을 해보겠습니다.

우리는 실행컨택스트의 코드가 어떤식으로 동작을 하고있는지 알고 있습니다. 간단하게 설명을 하면 컨택스트 스택에 컨택스트들이 차곡차곡 쌓이게 되고 이를 caller가 호출하면서 프로그램의 흐름을 관리를 하게됩니다. 

caller에 의해 호출은 되지 않지만 실행 컨택스트로 컨택스트가 추가가 되면 변수 객체에는 각 종 변수와 함수들이 프로퍼티로 정의 가되고 이때 모든 값은 undefined형태로 존재하게 됩니다.

변수객체 = {
     변수 : undefined,
     함수 : undefined,
     ...
}
이때를 우리는 실행컨택스트의 진입 단계로 봅니다. 그리고 이때를 변수들이 호이스팅이 된 상태라고 보고있습니다. 즉, 이미 해당 컨택스트의 코드가 실행이 되기 전에 변수객체와 실행객체의 프로퍼티는 undefined 또는 값으로 이미 생성이 된 상태입니다.

그리고 코드의 실행이 시작되면서 변수객체와 실행객체의 프로퍼티에 값들이 설정이 되기 시작합니다. 이게 앞서 호이스팅에서 이야기한 변수의 선언과 할당을 자바스크립트의 메커니증으로 설명을 한것입니다.


그럼 여기서 한가지 더! 
자바스크립트를 다루는 개발자들이 면접등을 볼때 많이 받는 질문중에 하나인 undefined와 not defined의 차이에 대해서 설명을 해보겠습니다.
console.log(A) // undefined
console.log(B) // error

B = "not defined";
var A = "undefined"

과연 어떤 차이가 있는 것일까요? 우선 해당 컨택스트에 집입을 했습니다. 이때 변수객체는 다음과 같습니다.

변수객체 = {
     A : undefined
}

엥?? 왜 B가 변수 객체에 존재하지 않는것일까요?? 앞에서 다룬 한가지 중요한 사실! 자바스크립트에서의 모든 변수는 var를 통해서만 생성이 된다는 말입니다. 즉 var를 통해서 선언된 변수만이 변수 객체에 추가가 될수있는 자격이 있습니다. 그럼 B는요??

B의 경우에는 변수객체가 아닌 글로벌 객체의 변수가 아닌 객체의 프로퍼티로 추가가 되어버립니다. 우리에게 변수는 실행객체속의 프로퍼티가 아니라 변수객체 내에 선언된 프로퍼티라는 사실을 명심해야 합니다.

결국 위 코드는 에러가 나고 코드를 수정해보면 다음과 같습니다.

console.log(A)

B = "not defined";
console.log(B)

var A = "undefined"

이렇게 코드를 작성하면  "not defined" 라는 결과를 나타내게 되고 이는 실제로 코드가 실행이 되는 단계에서 전역객체의 프로퍼티 접근 개념으로 값을 불러오게 되는것입니다.


그런데 여기서 문득 한가지 의문점이 생겼습니다. 변수 객체는 해당 함수의 스코프 상에서 존재를 하고 글로벌 변수객체를 제외하고는 독립적인 존재입니다. 즉 , 우리가 앞에서 배웠던 스코프의 개념입니다.
그럼 지역변수가 어떻게 전역변수의 값을 가지러 가는 것일까요? 즉 이런 형태일것입니다.

(function global(){
     var globalValue = "i'm global value";
     (function innerFunc1(){
         var innerValue = "i'm inner value";
          console.log(globalValue )
          (function innerFunc1(){
          console.log(globalValue )
          console.log(innerValue )
     })()
})()
})

위 코드의 결과는 예상처럼 "i'm global value" /  "i'm global value"   "i'm inner value" 입니다. 예전에는 당연하다고 느낀 위의 코드가 컨택스트와 실행 객체의 개념을 알고 나서는 이상해 보여야합니다. 독립적인 컨택스트가 어떻게 자신의 컨택스트를 벗어난 변수객체의 변수에 접근을 하는지에 대한 궁금증말입니다.


[스코프 체인(Scope chain)]
이 개념은 자바스크립트의 중요한 내용중의 하나인 프로토타입 체인과 매우 유사한 개념입니다. 프로토타입에 대한 내용은 [프로토타입 이해하기] 를 통해 확인하시면 됩니다.
간단히 설명을 하면 현재 스코프(컨택스트) 상에 변수가 존재하지 않는다면 부모 변수 객체로 이동을 해서 찾고 이를 계속해서 반복해 보통은 최상위 객체인 window 객체(전역변수객체)까지 접근해 변수를 찾는다는 내용입니다.
코드를 보겠습니다.

var x = 10;
(function foo(){
     var y = 20;
     (function bar(){
         var z = 30;
         console.log(x + y +z);
     })()
})();

위와 같은 코드는 바로 실행이 됩니다. 우리가 이코드에서 원하는 결과는 x + y +z 가 모두 더해진 값을 확인하게 되는 것입니다. bar() 함수가 만든 스코프상에는 z 라는 변수만 존재하고 y와 x 값을 찾기위해서 계속 부모로 이동하면서 변수를 찾게 됩니다. 여기서 x,y를 우리는 free valiables 로 표현할수 있습니다. 즉 bar() 입장에서는 부담없이 접근 가능한 변수입니다.
물론 최상단에 도착했을때 변수가 존재하지 않는다면 우리가 자주보는 undefinded 를 만나게 될것입니다. 그럼 위 함수를 변수 객체형태로 표현을 해보겠습니다.




함수의 실행객체는 __parent__ 라는 파라메터를 가지고 있습니다. 그리고 이 파라메터는 부모 객체를 참조하게 됩니다. 이는 프로토타입의 __proto__가 상위 포로토타입 객체를 참조하면서 부모의 성질을 그대로 이어받는것과 매우 유사한 개념입니다.



자 그럼 여기서 우리는 또다른 고민에 빠지게 됩니다. 뭐 일단 부모 변수객체가 모두 존재한다면 별로 특별할것 없이 내부 함수에서 부모쪽으로 검색을 하면서 필요한 변수를 찾는 것이 얼마든지 가능합니다. 스코프 체인이라는 개념을 통해서 말이지요. 그런데... 이 그림이 기억나시나요?


컨택스트는 해당 컨택스트가 끝이 나면 destroy 상태가 됩니다. 즉 제거가 됩니다. 담고있던 정보와 함께 말이지요..
그런데 아래와 같이 컨택스트정보가 스택에 저장에 되어있다고 가정해 보겠습니다.

[       EC2      ]
[       EC1      ]      [       EC1      ]
[ Global Context]      [ Global Context]

위와 같은 구조, 즉 EC2가 EC1의 내부 함수 이고 순서상 EC2가 먼저 실행이 완료되어 삭제가 됩니다. 그리고 EC1차례가 되었는데 내부 함수인 EC2를 리턴해야 한다면 이게 논리적으로 존재하지 않는 대상을 리턴해야되는 어처구니 없는 일이벌어집니다. 더구나 이 이후에 다른 컨택스트가 EC2를 활성화 시켜야 하는 경우도 있게됩니다. 존재하지 않는 컨택스트에 접근을 하고 활성화를 시키는 것이 말이 안되는 거죠..
결국 이미 없어진 객체를 계속적으로 사용하게 되는 오류가 발생하게 됩니다.

그래서 등장하게 되는 개념이 클로저(closure) 입니다.


[클로저(closure)]
자바스크립트에서 함수는 일급객체(first-class object)입니다. 즉, 함수를 다른 함수의 인자로 전달이 가능하다는 말입니다. 이렇게 전달되는 인자이름을 funargs 라고 부르는데 fuctional arguments의 줄임말입니다. 아무튼 funargs를 인자로 전달받는 함수를 higher-order function이라고 부릅니다. 또 함수가 다른 함수를 리턴하는 경우에는 이름 function valued function 이라고 부릅니다.

그런데 funargs와 function values를 사용하기에는 두가지 문제가 있고 이를 Funarg problem 이라고 부릅니다. 즉 함수가 다른 함수를 사용하게 되면서 생기는 문제, 혹은 다른 컨택스트의 정보를 사용하려고 시도하면서 생기는 문제를 말하는 것이고 이를 해결하기 위한 개념이 자바스크립트의  클로저(closure)입니다.

함수를 인자로 그러니 다른 함수를 사용하게 되면서 생기는 문제는 크게 두가지가 있습니다.

첫번째 문제는 “upward funargs problem”입니다.
이 문제는 함수가 다른 함수를 리턴하고 이미 선언된 변수를 사용하면서 발생합니다. 즉 변수를 찾기 위해서 이미 사라진 변수를 찾으로 부모 컨택스트로 접근을 하면서 발생하게 됩니다.
이를 해결하기 위해서 각 함수가 생성이 되는 순간, 즉 컨택스트가 생성이 되는 순간에 [[Scope]] 프로퍼티를 사용해서 부모의 정보를 저장해 둡니다.
그리고는 새로운 실행객체정보와 부모의 정보를 담은 [[Scope]] 정보를 조합해서 새로운 스코프체인 정보를 생성하게 됩니다.

Scope Chain = Activation object + [[Scope]]

이렇게 부모 컨택스트가 제거가 된 이후에도 상위 변수들을 참조할수 있는 방법을 마련해 두었습니다. 그럼 간단한 코드를 확인해 보겠습니다.

function foo(){
     var x = 10;
     return function bar(){
     c     onsole.log(x)
     }
}
var returnFunction = foo();
var x =20;
returnFunction();
-> 10
위의 코드를 보면 foo의 내부 함수인 bar()는 애초에 참조하고 있던 부모 변수x값을 그대로 유지하고 있음을 알수 있습니다. 이와 같은 스코프를 static 또는 lexical 스코프라고 부릅니다.

두번째 문제는 “downward funargs problem” 입니다.
이경우에는 첫번째와 다르게 부모 컨택스트는 존재합니다. 하지만 내부 함수가 어떤 변수를 사용해야 하는지 애매 모호한 경우가 있습니다. 정적으로 함수가 생성이 될때 만들어진 컨택스트의 변수 정보를 사용할것인지 아니면 실행시점에 동적으로 생성된 컨택스트의 변수 정보를 사용할것인지 하나를 선택해야 하는 상황이 발생합니다.
자바스크립트는 이 경우에는 무조건 정적으로 즉, 함수가 생성되는 시점에 저장한 정보를 사용하는것으로 정해두었습니다.
예를들어 보겠습니다.
var x = 10;
function foo(){
     console.log(x)
}

(function(funArg){
     var x = 20;
     funArg();
})(foo);

이경우에 funArg() 함수는 어떤 x를 사용해야하는지 애매모호할수 있으나 함수가 실행되는 시점이 아닌 생성이 되는 시점에 참조하는 x값을 그대로 사용하게 됩니다. 결과는 10이 될것입니다.

자바스크립트는 [[Scope]]라는 프로퍼티를 통해 클로저를 완벽하게 지원을 하고 있고 정적 스코프가 자바스크립트에서 클로저를 사용하는데 필수적인 요소라는 것입니다.

결국 클로저는 코드블럭과 statically / lexically 저장하고 있는 부모 스코프의 모든 정보의 조합의 개념입니다. 그렇기 때문에 함수는 자연스럽게 내부에서 부모의 변수들을 자연스럽게 접근을 할수 있습니다. 

그리고 이는 이론적으로 봤을때는 자바스크립트의 모든 함수는 생성이 되는 순간 [[Scope]] 프로퍼티를 가지고 있기 때문에 함수는 곧 클로저와 같다고 생각해도 됩니다.


자 그럼 또다른 상황에 대해서 이야기 해보도록 하겠습니다.
이런경우는 어떨까요? 하나의 부모 스코프를 가지고 여러 자식들이 같이 참조하는 경우입니다. 예를들면

function baz(){
    var x = 1;
    return {
        foo : function foo(){ return ++x},
        bar : function bar(){return --x}
    }
}
var closers = baz();
console.log(closers.foo() , closers.bar())
이 결과는 2와 1을 내놓습니다. 그럼 위 함수를 그림으로 표현해보겠습니다.



하나의 클로저가 다른 클로저의 변수에 영향을 주게됩니다.

또는 중복적으로 함수를 반복문을 통해 생성하는 경우에도 위와 같은 혼란을 줄수있습니다. 아래의 코드를 우선 보겠습니다.


var data = [];
for(var k=0 ; k < 3; k++){
data[k] = function (){
alert(k);
}
}
data[0]();
data[1]();
data[2]();

이세번의 호출은 어떤 값을 리턴할까요? 잠깐 생각해보겠습니다...
....
..아마 0, 1, 2???
답은 3, 3, 3입니다. 왜그럴까요? 분명 매번 돌때마다 그 값을 배열에 저장했고 당연히 0,1,2 가 나올거라고 생각했는데..

이런일이 벌어진 이유는 반복문을 돌면서 각기다른 스코프가 생성이 되어야하는데 이경우에는 전역스코프상의 k를 공유하면서 k값만 계속 변화가 일어나게 됩니다. 결국 공통으로 k를 바라보고 있어서 이런 일이 벌어지게 되었습니다.
우리가 원하는 0,1,2를 가지기 위해서는 각기다른 [[Scope]]를 가지게끔 해줘야합니다. 해결책은 의외로 간단합니다. 일반적으로 익명함수로 한번 감싸주고 인자로 필요한 값을 전달하는 방법으로 해결합니다.

var data = [];
for(var k=0 ; k < 3; k++){
     data[k] = (function(){
         return function (){
              alert(k);
         };
     })(k);
}
그리고는
data[0]();
data[1]();
data[2]();

이렇게 호출을 하면 원했던 0,1,2를 리턴하게 됩니다.


추가적으로 문득 위의 코드가 뭔가 깨림직하지 않으신가요? 함수안에 또 함수를 추가한다는 말인데.. 딱 봐도 처리속도나 쓸데 없이 메모리를 더 사용하게 될것같다는 생각이 드는건 왜일까요? 물론 위의 상황은 함수로 감싸는 방법말고는 딱히 방법이 없어보이기는 합니다만..

뭐 예를 들면 이런 코드입니다.
function Employee(name, phone, salary){
    this.name = name;
    this.phone = phone;
    this.salary = salary;
    this.getSalaryInfo = function(){
        return this.name + “’s salary : ” + this.salary;
    }
}
Employee이 생성자를 정의 했습니다. 그리고 생성자 안에 메서드를 정의 했습니다. 그런데 이 경우에는 생성자가 호출이 될떄마다 매번 메서드가 새롭게 할당이 됩니다. 메서드의 할당은 메모리에 실행객체의 생성, 컨택스트의 생성등 메모리의 낭비를 가져오게 됩니다.
무분별한 변수의 선언이나 함수의 선인을 피하는 것이 좋습니다. 그래서 자바스크립트의 프로토타입의 개념을 사용한다면 매번 새롭게 객체가 생성될때마다 메서드정의가 일어나지 않으니 이 특징을 잘 활용해서 코드를 작성하면 효율적인 프로그램이 작성이 될것입니다.

이제 마지막으로 실행컨텍스트 마지막 정보인 this에 대해서 알아보겠습니다.


[this]
this는 실행객체와 연관된 특별한 객체이므로 이름을 컨택스트 객체라고 부를수도 있습니다.
어떤 객체든 컨택스트를 값으로 this를 사용합니다. 여기서 한번더 실행컨택스트와 this 값 사이의 관계에 대한 오해에 대해서 명확하게 하고 넘어가겠습니다. 
종종 this는 변수객체의 프로퍼티로 잘못 설명이 됩니다. 한번더 명확하게 하자면 this는 실행 컨택스트의 프로퍼티입니다.

this가 중요한 이유는 this는 일반 변수와는 다르게 어떤 중간 매개 없이 , 스코프 체인 같은 중간 과정이 없이 바로 실행컨택스트에 접근을 합니다. this의 값은 오직 컨택스트에 들어오는 순간 딱 한번 정의 가 됩니다.
그래서 프로그램이 실행되는 도중에 this에 새로운 값을 부여하는것이 불가능합니다. 다시한번 말씀드리지만 일반 변수처럼 변수객체에 위치하지 않기 때문에 일반변수로 생각하면 안됩니다.

var x =10;
console.log(
     x,
     this.x,
     window.x
)

글로벌 컨택스트에서 this는 글로벌 객체 그 자체를 나타냅니다. 즉, 여기서의 this는 변수객체와 같음을 의미합니다.
글로벌 컨택스트 상에서의 this는 그다지 문제가 되지 않습니다.

대신 함수코드 내의 this가 이제부터 새로운 문제로 떠오르게 됩니다. 그럼 간단한 예제를 가지고 변하지 않을것이라고 생각한 this가 바뀌는 상황을 보여드리겠습니다.

var you = {name : "james"}

var me = {
     name : "alen",
     callme : function(){
          alert(this.name);
     }
}

me.callme(); // allen

you.callme = me.callme;

you.callme(); // james

분명 me의 callme에서의 this는 당연히 me를 가리키고 있었습니다. 그런데 me의 callme를 그대로 you의 callme로 정의 했더니 callme의 this가 me가 아닌 you를 가리키게 되었습니다. 분명 this는 한번 정의가 되면 바뀌지 않는 값이라고 했는데 어떻게 된일일까요??

이부분을 이해하기 위해서는 한가지 사실을 명심해야하는데 함수에서의 this는 해당 컨택스트의 코드를 활성화 시킨 caller에 의해서 제공이 됩니다. 즉 실행 컨택스트 스택에 있던 컨택스트가 caller에 의해서 호출이 되는 시점에 this가 결정이 되는데 이말은 함수를 호출하는 형태에 의해서 this가 결정이 된다는 말로 이해를 해도됩니다.

호출하는 표현식에 따라 this 값에 영향을 끼치게 됨을 이해하기 위해서 우리가 알아야하는 한가지 개념이 있는데 바로 레퍼런스 타입(Reference  type) 입니다.

레퍼런스 타입은 base와 property name이라는 두가지로 구성이 되어있는 객체입니다. 예를들면 

var foo = 10;
var fooReference = {
     base : global,
     propertyName : 'foo'
}

function bar() {}
var barReference = {
     base : global,
     propertyName : 'bar'
}

대충 레퍼런스 타입객체가 담고있는 정보에 대한 감을 잡으셨을것입니다.
 
var foo = {
  bar: function () {
    alert(this);
    alert(this === foo);
  }
};

위의 경우 우리가 bar 메서드를 호출하기 위해서는 dot notation이나 [""] 표현법을 사용할수 있습니다. 이때의 레퍼런스 타입의 형태는 다음과 같습니다.

foo.bar();
foo["bar"]();

var fooBarReference = {
  base: foo,
  propertyName: 'bar'
};

즉, bar라는 메서드의 기본이 되어주는 것이 foo임을 알수 있습니다. 함수의 호출괄호의 왼편에 레퍼런스 타입의 값이 존재한다면 this는 레퍼런스 타입의 base의 값을 가지게 됩니다. 이를 제외한 모든 경우에는 this가  null 값을 가지는데 null이 아닌 전역객체로 대체되어 값을 가지게 됩니다.

다시 한번 정리를 하면 내가 호출하는 함수가 어떠한 객체를 기반으로 동작을 한다면, 즉 base가 해당 객체를 값으로 가진다면 this는 그 객체를 값으로 가지게 되며 이 경우를 제외한 나머지 모든 경우에는 전역객체를 값으로 가지게 됩니다.

function foo() {
  return this;
}

foo(); // global
var fooReference = {
  base: global,
  propertyName: 'foo'
};



var foo = {
  bar: function () {
    return this;
  }
};

foo.bar(); // foo
var fooBarReference = {
  base: foo,
  propertyName: 'bar'
};


var test = foo.bar;
test(); // global
var testReference = {
  base: global,
  propertyName: 'test'
};


jQuery를 사용할때 $.each() 같은 함수를 사용할때 왜 $.를 앞에 붙여서 사용하는지 이제는 알것같습니다.

이제 함수를 호출하는 방식에 따라 다른 this를 가지는 이유와 짜여진 코드에서 this가 무엇을 의미하는지를 구분할수 있게 되었을거라 믿습니다. 이렇게 this의 값이 변화무쌍하다 보니 jQuery같은 라이브러리에서는 this를 $(this)로 바꿔 무조건 해당 함수내부에서의 this를 함수객체 자체로 바라보게 만들어서 코딩을 하기도 합니다.






엄청나게 긴글이 되어버렸습니다. 저도 원문인 http://dmitrysoshnikov.com/ecmascript/javascript-the-core/ 를 보면서 몰랐던 내용들을 공부하고 찾아보면서 쓰다보니 어쩔수 없이 길어졌습니다. 그래도 자바스크립트를 제대로 이해하기위해서는 반드시 한번쯤은 읽어보시는걸 추천드립니다. 원문을 읽으셔도 물론 훌륭합니다.

최근 자바스크립트관련된 기술들이 엄청나게 쏟아져나오고 있습니다. 그 기술들을 사용을 하는데 있어서 왜 그런식으로 사용을 하게 하며 그 뒷단에서 자바스크립트 그 자체가 하는 역활이 무엇인지를 제대로 이해하기 위해서는 테크닉적인 정보가가 아닌 자바스크립트의 가장 기본적이고 핵심적인 내용이 무엇보다 중요합니다. 항상 기본에 충실해야 흔들리지 않는법이겠죠?? 아님 뭐 별수 없지만요..

항상 피드백은 대환영입니다.



참고



출처: https://yubylab.tistory.com/entry/자바스크립트-변수로-자바스크립트-이해하기 [Yuby's Lab.]

반응형
반응형

최근 들어 node.js를 이용한 서버사이드 프로그래밍이 증가하면서 callback함수에 대한 이해도를 높일 필요가 있었습니다.
우리가 DOM을 다룰때 가장 흔히 사용하는 라이브러리인 jQuery도 대부분의 이벤트를 callback 방식으로 처리를 하고 있습니다. 아무런 생각없이 사용하던 callback 함수의 이해와 제대로된 사용을 위해 공부하던중 좋은 자료가 있어 번역 및 추가적인 내용들을 추가해 보았습니다.

출처는 : http://javascriptissexy.com/understand-javascript-callback-functions-and-use-them/ 입니다.


1 .자바스크립트의 콜백함수의 이해와 사용법 (Learn JavaScript Higher-order Functions—Callback Functions)

자바스크립트에서 함수는 일급객체입니다. 일급 객체가 되기 위해서는 몇 가지 조건을 만족하여야 합니다.

  1. 변수나 데이터 구조 안에 담을 수 있다.
  2. 파라미터로 전달 할 수 있다.
  3. 반환값으로 사용 할 수 있다.
  4. 런타임에 생성될 수 있다.

자바스크립트에서 함수가 일급객체이기 때문에 우리는 함수를 인자로 사용하는 콜백패턴을 자바스크립트에서 사용할 수가 있습니다.

이제부터 우리는 콜백함수의 모든것에 대해서 알아보겠습니다.
자바스크립트에서 콜백함수는 가장 널리 사용되는 기법이고, 이를 이용해 jquery나 각종 자바스크립트로 만들어진 라이브러리에서 사용되고 있는 것을 확인 할 수 있습니다.
만약 자바스크립트를 개발하는 입장에서는 여전히 콜백함수가 명확하게 개념이 잡혀있지 않다면 이번기회에 확실히 잡으시길 바랍니다.

콜백합수는 함수형 프로그래밍에서부터 발생한 패러다임입니다. 간단히 말해 함수형 프로그래밍은 함수를 인자처럼 사용을 하는 것인데 이는 소수의 전문가들만이 이해하고 사용하는 기법이었지만 지속적으로 누군가에 의해 설명되고 분석되어 오면서 우리가 쉽게 이해하고 사용 할 수 있는 수준이 되었습니다.
그중에서 가장 일반적인 기술 중 하나가 바로 콜백함수 입니다.
다시한번 간략하게 설명하면 함수를 인자로 넘겨 사용하겠다 입니다.

2 .콜백함수 또는 Higher-order Function이라는게 무슨말일까요? (What is a Callback or Higher-order Function?)

콜백함수 higher-order function 이라고 알려진 이 녀석은 파라메터로 함수를 전달하는 녀석입니다. 그리고 전달받은 그 함수를 함수의 내부에서 실행시킵니다.
그럼 Jquery에서 사용하는 간단한 콜백함수의 예를 볼까요?

$("#btn_1").click(function() {
     alert("Btn 1 Clicked");
});

보시는것처럼 우리는 click 함수의 인자로 함수를 전달하고 있습니다. 그리고 그 함수는 click 함수가 실행이 되면 동작을 합니다. 위와 같은 형태가 가장 전형적인 자바스크립트의 콜백함수 입니다.
또 다른 매우 전형적인 콜백함수의 예제가 있습니다.

var friends = ["Mike", "Stacy", "Andy", "Rick"];
friends.forEach(function (eachName, index){
     console.log(index + 1 + ". " + eachName); // 1. Mike, 2. Stacy, 3. Andy, 4. Rick
});

forEach라는 함수안에 익명의 함수를 넣어서 forEach 구문 내에서 동작을 하게 하고 있습니다.
일단 여기까지 우리가 얼마나 거리낌 없이 콜백함수를 사용하고 있었는지 알게 되었습니다. 그럼 실제 콜백함수가 어떤식으로 동작을 하는지에 대해 집중해 보겠습니다.

3.콜백함수는 어떻게 동작하는가? (How Callback Functions Work?)

자바스크립트에서 모든 것은 객체입니다. 심지어 함수도 객체입니다. 일급객체! 그래서 우리는 함수를 변수처럼 그리고 다름 함수의 리턴값으로 사용이 가능하게 됩니다. 함수를 콜백으로 다른 함수의 인자처럼 사용 할 경우에는 오직 합수의 정의만을 넘겨주면 됩니다.
즉, 함수의 이름만을 넘겨주면 됩니다. 굳이 함수라고 하여 끝에 () 같은 것을 붙여줄필요가 없습니다. 다음과 같은 형태입니다.

setInterval(callback, 1000)

위의 코드 처럼 함수의 인자로 전달된 함수의 경우에는 언제든 원하는 시점에 실행을 시킬수가 있습니다.
이게 매우 중요한 점인데 콜백함수는 전달 받은 즉시 바로 실행이 될 필요가 없다는 말입니다. 함수의 이름처럼 “called back” , 함수의 내부의 어느 특정시점에 실행을 합니다. 처음에 봤었던 jquery의 예제에서도 봤지만

$("#btn_1").click(function() {
     alert("Btn 1 Clicked");
});

익명의 함수는 click이라는 함수의 내부에서 나중에 호출이 됩니다. 아무런 이름이 없지만 이 함수를 담고 있는 click 이라는 함수는 언제든지 이 함수를 호출할 수가 있습니다.

4. 콜백함수는 클로저다(Callback Functions Are Closures)

우리가 다른 함수의 인자로 콜백함수를 전달할 때, 전달받은 함수의 특정시점에 그 콜백함수가 동작을 할 것입니다. 마치 전달받은 함수가 이미 콜백함수를 내부에서 정의 한 것처럼 말이지요.
이 말은 콜백은 클로저라는 말과 같습니다. 클로저에 대한 자세한 내용은 자바스크립트-변수로-자바스크립트-이해하기 에서 확인이 가능합니다.
간단하게 설명을하면 전달된 콜백함수는 콜백함수를 포함한 함수 내부의 인자에 접근이 가능하고 심지어 전역변수에도 접근이 가능한 상태가 됩니다. 즉, 기존의 함수가 가진 스코프에 새로운 내부 스코프가 추가가 되는 것입니다. 이로 인해 발생하는 이슈가 있는데 자세한 내용은 자바스크립트-변수로-자바스크립트-이해하기 에서 확인하시기 바랍니다.
우리가 알다시피 클로저는 포함하고 있는 함수의 스코프에 접근을 할수 있습니다. 그래서 콜백함수는 포함하고 있는 함수의 변수에 접근이 가능하고 심지어 전역변수에도 접근이 가능합니다.

5. 콜백함수 적용의 기본 원칙(Basic Principles When Implementing Callback Functions)

콜백함수는 복잡하지 않지만 적용하기 위한 몇가지 원칙이 있습니다.

이름이나 익명의 함수를 사용하라(Use Named OR Anonymous Functions as Callbacks)

위에서 본 jquery나 foreach함수의 예제에서 보았듯이 우리는 익명의 함수를 파라메터로 사용을 합니다. 이러한 방식이 가장 일반적인 패턴입니다. 또 다른 보편적인 방법으로는 함수를 정의해 해당 함수의 이름을 파라메터로 넘기는 방식입니다.

var allUserData = [];

// 콘솔에 결과를 찍는 함수
function logStuff (userData) {
    if ( typeof userData === "string")
    {
        console.log(userData);
    }
    else if ( typeof userData === "object")
    {
        for (var item in userData) {
            console.log(item + ": " + userData[item]);
        }

    }

}

// 두 개의 인자를 받아서 마지막에 콜백함수를 호출한다.
function getInput (options, callback) {
    allUserData.push (options);
    callback (options);

}

// getInput 함수를 호출할 때 , 우리는 logStuf이라는 함수의 이름을 인자로 넘긴다.
// 그래서 logStuff 은 콜백함수가 되어 getInput이라는 함수의 내부에서 동작을 할것이다.
getInput ({name:"Rich", speciality:"JavaScript"}, logStuff);
//  name: Rich
// speciality: JavaScript

콜백함수로 파라매터 전달(Pass Parameters to Callback Functions)

콜백함수가 실행이 될 때는 그냥 일반 함수와 동일하게 동작을 합니다. 그래서 우리는 콜백함수에 파라메터를 전달할 수가 있습니다. 우리는 파라메터로 콜백함수를 감싸고있는 함수 내부의 어떠한 프로퍼티라도 파라메터로 전달할 수가 있습니다. 아래의 예제를 실행하면 options 파라메터를 콜백함수에 전달할수 있습니다. 전역변수와 지역변수를 파라메터로 전달할 수 있습니다.

//전역변수
var generalLastName = "Clinton";

function getInput (options, callback) {
    allUserData.push (options);
// 전역변수를 콜백함수의 인자로 전달한다.
    callback (generalLastName, options);
}

콜백함수가 실행 되기 전에 함수임을 명확하게 하기(Make Sure Callback is a Function Before Executing It)

콜백함수가 인자로 전달되어 함수의 내부에서 실행이 될 때 전달받은 인자가 함수인지를 명확하게 정의 하고 실행하는 것이 좋은 습관이다.
위의 함수를 고쳐보겠습니다.

function getInput(options, callback) {
    allUserData.push(options);

    // callback 이 함수 인지를 확인합니다.
    if (typeof callback === "function") {
    // callback 이 함수인지를 확인 했으니까 함수호출합니다.
        callback(options);
    }
}

만약에 이러한 확인 작업이 없다면 callback파라메터를 넘기지 않거나 혹은 함수가 아닌 값을 넘기게 되는 경우에는 실행중에 에러가 발생하는 문제가 일어납니다.

this를 사용한 메서드를 콜백으로 사용시 문제 (Problem When Using Methods With The this Object as Callbacks)

콜백함수가 this객체를 사용하는 메서드인 경우에는 우리는 반드시 this객체의 컨택스트를 보호할 수 있도록 콜백함수를 수정해야 합니다.
예를들면 전역함수에 인자로 콜백함수가 전달된 경우에는 this객체가 window객체를 가리키게 만들거나 또는 콜백함수를 감싸고 있는 메서드를 가리키게 해야합니다.

//객체를 생성합니다.
// 다른 함수의 콜백함수로 전달한 메서드를 정의합니다.

var clientData = {
    id: 094545,
    fullName: "Not Set",
    // setUserName clientData의 메서드입니다.
       setUserName: function (firstName, lastName)  {
        // this는 clientData라는 객체를 지칭하고 있습니다.
      this.fullName = firstName + " " + lastName;
    }
}

function getUserInput(firstName, lastName, callback  )  {

    // Do other stuff to validate firstName/lastName here

    // Now save the names
    callback (firstName, lastName);
}

getUserInput ("Barack", "Obama", clientData.setUserName);

console.log (clientData.fullName);// 값에 설정되지 않음

// fullName 프로퍼티가 window object의 인자로 세팅됨
console.log (window.fullName); // Barack Obama

이 코드예제에서 clientData내의 setUserName을 실행하면 예상과 달리 clientData내의 this.fullName 의 값으로 세팅이 되지 않습니다.
대신에 window 오브젝트의 값으로 세팅이 되어버리는데 이는 getUserInput메서드가 글로벌 함수이기 때문이다. 이러한 일이 발생하는 원인은 this라는 객체가 window객체라는 글로벌한 객체를 가리키고있기 때문이다.
이게 자바스크립트가 가진 문제점 중에 하나입니다. 작성자가 생각한 this가 때로는 의도치 않게 다른 객체로 나타나게 되는 경우가 있습니다.
이러한 문제를 피하기 위해 jQuery에서는 $(this) 를 사용하거나 커피스크립트에서는 내부에서 처리를 해주고 있습니다.

Call 과 Apply를 통한 this 보호 (Use the Call or Apply Function To Preserve this )

우리는 Call과 Apply 함수를 통해 이러한 문제를 해결할 수 있습니다. 일단 모든 자바스크립트의 함수에는 Call 과 Apply라는 메서드를 가지고 있습니다. 그리고 이 함수들은 함수 내부에서 this객체를 유지하게 하고 인자들은 함수로 전달하는 역할을 합니다.
Call함수는 항상 첫번째 인자로 this 객체를 사용합니다. 그리고 나머지 인자들은 콤마로 구분하여 보이지 않게 전달을 합니다.
Apply 함수의 경우에도 첫번째 인자로 this객체를 사용합니다. 하지만 마지막 파라메터의 경우 값들이 배열형태로 존재합니다.
이게 보면 말은 매우 복잡합니다. 하지만 코드를 통해 보시면 쉽게 이해가 될것입니다. 이전의 문제가 있었던 코드를 고쳐보겠습니다. 우리는 우선 Apply함수를 사용해보겠습니다.

//callbackObj라는 파라메터를 추가했습니다.
function getUserInput(firstName, lastName, callback, callbackObj)  {
    // Do other stuff to validate name here

    // 아래의 apply함수는 callbackObj에 this객체를 매핑합니다.
    callback.apply (callbackObj, [firstName, lastName]);
}

Apply함수를 통해 this 객체를 제대로 정의 할 것입니다. 우리는 이제 실제 getUserInput을 실행을 하면 제대로 clientData의 값을 세팅하는 것을 확인할수 있습니다.


// 우리는 clientData.setUserName 메서드와 clientData 객체를 파라메터로 전달합니다. clientData객체는 Apply함수에의해 this 객체로 정의가 될 것입니다. 

getUserInput ("Barack", "Obama", clientData.setUserName, clientData);

// 제대로된 결과를 리턴합니다.
console.log (clientData.fullName); // Barack Obama

이러한 동작은 Call 함수를 통해서도 그대로 동작하게 될것입니다.

6 .다중 콜백함수(Multiple Callback Functions Allowed)

우리는 여러 개의 콜백함수를 파라매터를 전달할 수 있습니다. 아래의 코드는 전형적인 jquery의 ajax 함수 들입니다.

function successCallback() {
    // Do stuff before send
}

function successCallback() {
    // Do stuff if success message received
}

function completeCallback() {
    // Do stuff upon completion
}

function errorCallback() {
    // Do stuff if error received
}

$.ajax({
    url:"http://fiddle.jshell.net/favicon.png",
    success:successCallback,
    complete:completeCallback,
    error:errorCallback

});

7. 콜백 지옥의 문제와 해결책 (Callback Hell” Problem And Solution)

간단한 실행을 위한 비동기적인 코드는 가끔 수많은 콜백함수의 연속으로 이어지는 경우가 있습니다. 아래의 코드를 보시다시피 매우 지저분합니다. 이른바 콜백지옥입니다. 이러한 코드는 매우 가독성이 떨어집니다.
아래의 코드의 경우 node-mongodb-native 에서 가져온 예제입니다.

var p_client = new Db('integration_tests_20', new Server("127.0.0.1", 27017, {}), {'pk':CustomPKFactory});
p_client.open(function(err, p_client) {
    p_client.dropDatabase(function(err, done) {
        p_client.createCollection('test_custom_key', function(err, collection) {
            collection.insert({'a':1}, function(err, docs) {
                collection.find({'_id':new ObjectID("aaaaaaaaaaaa")}, function(err, cursor) {
                    cursor.toArray(function(err, items) {
                        test.assertEquals(1, items.length);

                        // Let's close the db
                        p_client.close();
                    });
                });
            });
        });
    });
});

이 함수를 간단하게 설명을 하면 우선 몽고디비 서버를 열어 해당 데이터 베이스의 내용들을 삭제를 시킵니다. 그리고 그 삭제된 데이터 베이스를 가지고 ‘test_custom_key’ 라는 이름의 컬렉션을 생성합니다. 생성된 컬랙션에 데이터를 삽입하고 “aaaaaaaaaa”라는 아이디를 가지는 값을 찾습니다. 이러한 처리가 끝이 나면 그 데이터를 처리하고 DB를 close 시킵니다.
아마 이러한 코드를 여러분의 코드에서는 만나지 못할 수도 있습니다. 하지만 node.js 로 서버사이드를 처리하다 보면 의도치 않게 이러한 처리들을 하게 되는 경우가 빈번하게 있습니다.
이러한 콜백지옥에서 탈출하기 위해 2가지 해결책이 있습니다.

  1. 메인함수에서 익명의 함수 형태로 인자가 되어 전달되게 하지말고 함수에 이름을 줘서 변수화 시켜 그 함수의 이름을 인자로 전달을 시킵니다.
  2. 모듈화를 시킵니다. 특정작업을 위한 모듈로 분리시켜 필요할 때 불러서 사용하는 형태로 만듭니다.
    


    마지막로 정리를 하며 콜백함수를 사용할 때 주의사항을 말씀드리면

  • 코드를 중복하여 사용하는 형태를 피합니다.
  • 추상화를 제대로 시켜 더 일반적인 형태로 다양하게 사용될 수 있도록 만듭니다.(특정 형태가 아닌 제너럴한 형태로 만듭니다.)
  • 유지가 잘될 수 있도록 만듭니다.
  • 읽기 쉽게 만듭니다.
  • 함수는 더욱 특정화 시킵니다.
    이러한 주의사항을 지킨다면 콜백함수를 제대로 사용할 수 있을것입니다.
    실제 원문에는 자신만의 callback함수 만드는 내용이 있지만 개념을 설명하기에는 이걸로 충분할듯합니다. 역시 뭐든 제대로 알고 써야한다는 생각이 듭니다.


출처: https://yubylab.tistory.com/entry/자바스크립트의-콜백함수-이해하기 [Yuby's Lab.]

반응형
반응형


◎ 함수의 선언

▼ 내용

함수 선언식 - Function Declarations

일반적인 프로그래밍 언어에서의 함수선언과 비슷한 형식이다.

// 예시 function funcDeclarations() { return 'A function declaration'; } funcDeclarations(); // 'A function declaration'

함수 표현식 - Function Expressions

일반적인 프로그래밍 언어에서의 함수선언과 비슷한 형식이다.

var funcExpression = function () { return 'A function expression'; } funcExpression(); // 'A function expression'

◎ 함수의 구분 (객체 안에 함수)

▼ 내용

객체안에 함수가 들어있으면 메소드 입니다.

var a = { b : function (){ return '3'; } } // a.b() = 3 // function 의 익명함수는 메소드 function ag(){ return '3'; } // 위의 함수는 함수

◎ 콜백함수 (변수 = 전부 객체)

▼ 내용

함수 , 변수들은 전부 객체 입니다.

그렇기 때문에 함수는 값으로도 사용할수있습니다.

function ab(){ return 3; } var abcd = 3+ab(); console.log(abcd); //abcd = 6

​이렇게 ab()함수는 값으로 사용할수 있습니다. 이거와 비슷하게 함수는 값이기 때문에

인자로 전달 받을수도 있습니다.

function cal(func,num){ return func(num); } function increase(num){ return num+1; } console.log(cal(increase,2)); //3

위 처럼 cal 안에 또다른 함수를 넣는것을 콜백함수라 부릅니다.

또한 함수는() 이렇게 쓰면 실행이되지만 그냥 함수의 이름만 넣어줄경우엔

함수의 글자들만 넘어가서 마더함수가 실행될경우 로직에따라 콜백함수를 실행합니다.

또한 함수도 값이기 때문에 콜백함수도 값으로 취급하여 계산식등을 사용할수 있습니다.

함수는 이처럼 변수 , 매게변수 , 리턴값에 사용할수있는데 이러한 것들을

프로그래밍에서는 first-class citizen 또는 first-class object 등 뭐 이렇게 부릅니다.


◎ 클로저

▼ 내용

클로저는 (closure)는 내부함수가 외부함수의 맥락(context)에 접근할 수 있는 것을 가르킨다.

내부함수는 외부함수의 지역변수에 접근 할 수 있는데 외부함수의 실행이 끝나서 외부함수가 소멸된 이후에도 내부함수가 외부함수의 변수에 접근 할 수 있다. 이러한 메커니즘을 클로저라한다.



반응형
반응형

자바스크립트는 html 위에서 작동한다

html을 동적으로 작성하기 위해 자바스크립트라는 기술이 나왔습니다.

자바스크립트 데이터타입

1. Boolean

2. Null

3. Undefined

4. Number

5. String

6. Symbol (new in ECMAScript 6)

'문자열'.toUpperCase // 문자열을 대문자로 치환

'문자열'.indexOf('찾을문자') 찾을문자가 문자 몇번째에서부터 시작되는지 숫자로 표시됨

'문자열'.trim // 문자의 공백을 제거

x = 1;

대입연산자 = (우항의값을 좌항에 대입)

변수 : variable (베리어블) 이라고도 한다

html 에서 비교연산자 &it; 는 < 이렇게 표시된다

함수를 만드는 3가지 방법

//1. var showAll = function(){ } //2. function showAll() { } //3. var cow = {}; cow.showall = function(){ console.log("나다") } // 1번 2번은 같다고 보면된다 3번은 cow 라는 객체에 showall 이라는 함수를 넣은것이다

객체 내가 생각하는 객체는 변수 함수 메소드 모든게다 각각 객체가 될수있다

객채에 함수를 넣으면 그 함수는 메소드가된다

위 그림의 3번같은 경우

var showAll = {'abc' , 'abc1'};

이럴 경우 객체가 생성이된다.

또한 showAll 이라는 객체의 'abc' 는 프로퍼티 라고도 한다.

3번의 showall 에 들어있는 함수에서 this를 사용하면 cow를 뜻한다

함수 안에서 this를 쓰면 함수바로 상위의 객체를 this 로 쓴다

만약 2번처럼 쓰고 그안에 this 를 쓸경우 그 this 는 상위 객체? 인 window를 소환한다


반응형
반응형





자바스크립트는

var a = '1' alert(a) var a = '1'; alert(a);

이렇게 줄바꿈 하면 문장이 끝낫다고 알기때문에 ; 안붙혀도 작동이노딘다

하지만 ; 을 명시적으로 적어주는게 좋다

var a = 1 //여기서

a //는 변수

1 //은 값

= //은 대입연산자

//라고부른다

이런걸 이항 연산자라고 한다.

또한 비교문

= 는 대입연산자 우항의 값을 좌항에 넣는다

== 비교연산자 좌항과 우항의 같냐 틀리냐

=== 비교연산자 좌항과 우항의 값이 정확히 같냐 틀리냐

1 == "1" //true 데이터 타입이 달라도 정보의 값이 같아서 true

1 === "1" // false 정보의 값이 아니라 타입까지 일치해야한다

null 과 undefined는 값이 없다라는 뜻이다.

null // 값이 없다

undefined 정의되어있지 않다.

NaN 은 0을 0으로 나눳을때 (성립하지 않는다)

NaN === NaN // False



== 비교 연산자 일때

true 면 녹색

false 면 흰색 표시

자바스크립트 데이터 비교



=== 비교연산자 일때

true 면 녹색

false 면 흰색 표시

자바스크립트 데이터 비교

for 문에 i++ 는 실행을 하고 1씩증가하고

++i 는 증가한다음실행이된다

배열합치기

var li = ['a','b']

li.concat(['f','g'])

이렇게 되면 배열이 합쳐진다.




.unshift 를 쓰면 배열의 맨앞으로 추가된다.

배열.splice([인덱스],0,"배열에추가할문자")

배열 제거

배열.shift 첫번째 원소를 제거

배열.pop 끝번째 원소를 제거

배열정렬

배열.sort(); 하면 순서대로 정렬된다 디비 asc; 같은 느낌

배열.reverse(); 하면 거꾸로 정렬된다 디비desc;

객체



var grades = new Object(); 이것과 var grades = { }; 은 같은의미이다.


반응형

+ Recent posts