오늘은 변수와 관련된 내용을 한 번 더 체크해보려고 합니다.
이 부분을 진행한 다음에는 함수의 개념과 구조에 대해서 체크하도록 하겠습니다.
문제 하나 내보겠습니다. 다음 코드를 보고 결과물이 무엇이 나올 것 같습니까?
function wrapElements(a) {
var result = [] , i , n ;
for (i=0, i<a.length; i <n ; i++){
result[i] = function() { return a[i]; };
}
return result;
}
var wrapped = wrapElements([10,20,30,40,50]);
var f = wrapped[0];
console.log(f);
함수 wrapElements의 리턴 값인 배열의 0번째 요소가 f의 값이 되어 출력될 것이라 생각하여 10을 고르시진 않았나요?
아쉽게도 틀리셨습니다 결과는 undefined 입니다.
이유가 무엇일까요? 앞서 배운 내용의 클로저 개념이 여기에 다시 들어갑니다.
wrapElements라는 함수 내부에 선언한 지역변수인 result 배열과 i,n 변수는 잘 참조되어 표현되게 됩니다.
(참고로 반복문을 많이 만들 떄에는 함수 내부 , 반복문 외부에 i 와 n 을 별도로 선언하여 진행하는 것이 좋습니다)
--> 변수 호이스팅 개념 ( 재선언한 변수는 새로 생긴 변수와 다름없다 . __불필요한 선언 )
이 부분을 확실히 이해하기 위해선 바인딩과 할당의 차이점을 이해해야 한다.
런타임 시에 스코프에 진입하면 해당 스코프에 있는 변수들을 바인딩하기 위해 메모리에 '슬롯'을 할당한다.
wrapElements 함수는 세 지역 변수 result,i,n을 바인딩한다.
따라서 이 함수가 호출되면 wrapElements 함수는 이 세 변수들을 위한 슬롯을 할당한다.
반복문을 순회할 때마다 반복문의 본문은 감싸는 함수를 위한 클로저를 할당한다.
여기서 오류가 발생하는데 감싸진 함수가 ( function() { return a[i]; }; ) 생성되는 시점에 그 함수가 i의 값을 명백히 저장하고 있다는 기대 때문에 생기게 된다.
반복문에 감싸진 함수 ( function() { return a[i]; }; ) 에서 i는 값을 할당 받는 것이 아니라 참조 받는 것이다.
즉, i가 내부 함수에선 외부 변수로 보이는 것이고, 외부 변수 i의 값의 참조값을 저장하게 되는 것이다.
참조 값을 저장하기 때문에 i는 값이 계속해서 변하기 때문에 내부 함수는 결국 i의 마지막 값을 바라보게 됩니다.
따라서 wrapElements 함수를 받는 wrapped 같은 클로저는 반복문 이전에 i를 위해 생성된 하나의 공유 슬롯을 참조하게 된다.
i는 a의 크기 만큼 들어갈 슬롯을 미리 준비해두는 것이고 반복문을 순회할 때마다 i 값은 배열의 마지막에 도달할 떄 까지 증가하게 됩니다.
클로저 i를 호출할 떄에는 배열의 다섯 번째 인덱스를 찾게 되어 undefined가 리턴되는 것입니다.
반복문이 돌면서 i의 값은 계속해서 바뀌고 이 값을 받는 것이 아니라 i의 참조 값을 가지고 오는 것이기 때문에 반복문이 다 돌고 난 뒤에
마지막 값을 가져오려하니 undefined가 나오는 것이다.
wrapped[0] 을 아무리 찾아도 이미 참조 값은 마지막을 가리키고 있고 거기의 값을 찾기 때문에 undefined가 뜨는 것..!!
이를 방지하기 위해선 반복문이 돌아가면서 함수가 생성될 때 i의 참조 값을 실제로 활용하기 위해서 함수 내에 지역변수를 써야한다.
또한 즉시 값을 출력할 수 있게 즉시 실행 함수 (IIFE)를 사용한다.
function wrapElements(a){
var result = [];
for(var i=0 , n=a.length; i<n ; i++) {
(function(){
var j = i;
result[i] = function() { return a[j] };
})();
}
}
function wrapElements(a){
var result = [];
for(var i=0 , n=a.length; i<n ; i++) {
(function(j){
result[i] = function() { return a[j] };
})(i);
}
}
다음과 같이 코드를 정리하면 j 값을 내부 변수로 참조해 올 때 (클로저) 지역 변수 개념으로 들어가기 때문에 함수 내에선 값을 할당 받은 거라 생각하면 된다.
j는 내부 변수로 참조한 것이기 때문에 이를 결과물로 리턴해야 값이 올바르게 나올텐데, 즉시 실행 함수로 표현식을 만들어서 다음과 같이 진행 해야 반복문이 모두 돌기 전에 들어오는 즉시 호출한 함수의 인덱스에 맞게 값이 반환되어 출력됩니다.
아래 이미지를 참조하면 조금 더 이해하기 쉬울 것 같습니다.
wrapped[0]을 호출할 때
wrapped[1]을 호출할 때
호출한 인덱스에 맞게 들어오는 즉각 값이 출력되며 (즉시 실행 함수 표현식으로 작성하였기 때문)
그 값이 온전하게 나오도록 하기 위해서 j 라는 변수를 반복문 내에 위치한 함수에 두어 내부 변수 , 지역 변수 개념으로
활용하게 만든 것이다.
i 값은 외부 변수이기 때문에 해당 값 자체를 가져오는 것이 아니라 참조하는 것이기 때문에 실체를 가져올 순 없지만,
j 값으로 i 값을 인계 받아서 반환 함수 스코프 내의 지역 변수로 사용하기 때문에
값 반환시 유의미한 값이 나오게 되는 것입니다.
이번 내용은 생각보다 복잡하고 어려운 것 같습니다.
잘 정리하고 숙지해주시면 좋을 것 같습니다 !
ps. 반복문의 조건에 var를 활용한 변수 선언도 반복문 내 함수에선 외부 변수로 참조하기 때문에 값이 아닌 참조를 저장하는 것입니다.
'Node.js & Javascript' 카테고리의 다른 글
S3 사용법 (2) | 2018.04.06 |
---|---|
module 관리 (0) | 2018.03.15 |
자바스크립트 함수(1) (0) | 2018.03.08 |
Javascript 변수 (0) | 2018.03.06 |
FCM 푸시 [서버편] (0) | 2018.03.01 |