dinist

[JavaScript] 반복문에서의 내부함수 사용시 유의사항 본문

Web/JavaScript

[JavaScript] 반복문에서의 내부함수 사용시 유의사항

dinist 2022. 12. 13. 17:29

반복문에서의 내부함수 사용시 유의사항에 대한 기록을 남기게 된 이유는

var 키워드를 사용한 변수의 반복처리도중 만났던 문제를 해결하며 기록을 해야겠다는 생각에서부터 시작됐습니다.

 

예를 들어 설명해보겠습니다.

product_info 객체는 상품코드가 Key이고, 하위코드, 하위단위가 각각 배열로 저장되어 있는 객체가 Value입니다.

 

상품코드와, 하위코드, 하위단위를 Back-End에 AJAX 요청을 하면 하위코드와 하위단위를 결합한 결과를 json으로 리턴하는 코드입니다.

 

Back-End에 요청하는 자바스크립트 코드입니다.

예시를 위해 Promise, 화살표 함수등을 사용하였습니다. 실무에서는 기타 이유로 ES6 문법을 사용할 수 없습니다.

사실 반복문에서 이러한 문제는 구글에서 찾아보면 꽤나 많은 블로그 포스팅을 찾아볼 수 있습니다.

var product_info = {
    "PRODUCTCODE1" : {
        "subcode" : ["subcode11","subcode12"],
        "subunit" : ["subunit12","subunit12"],
    },
    "PRODUCTCODE2" : {
        "subcode" : ["subcode21","subcode22"],
        "subunit" : ["subunit21","subunit22"],
    },
}

var product_merge_info = {}

new Promise((resolve,reject) => {
    for(var key in product_info){
        $.ajax({
            url : "./Backend.php",
            type : "POST",
            dataType : "json",
            cache : false,
            data : {
                'prdcode' : key,
                'subcode' : product_info[key]['subcode'],
                'subunit' : product_info[key]['subunit'],
            }
        })
        .done(function(result){
            product_merge_info[key] = result['result']
        })
        .fail(function(result){
            console.log(result)
        })
    }
    resolve(product_merge_info)
}).then(() => {
    console.log(product_merge_info);
})

AJAX 요청을 처리하는 Back-End 코드입니다. PHP를 사용하였습니다.

<?php

$prdcode = isset($_REQUEST['prdcode']) ? addslashes($_REQUEST['prdcode']) : "";
$subcode = isset($_REQUEST['subcode']) && is_array($_REQUEST['subcode']) ? array_map(function ($value) { return addslashes($value); },$_REQUEST['subcode']) : array();
$subunit = isset($_REQUEST['subunit']) && is_array($_REQUEST['subunit']) ? array_map(function ($value) { return addslashes($value); },$_REQUEST['subunit']) : array();

if (empty($prdcode) || count($subcode) === 0 || count($subunit) === 0){
    die(
        json_encode(
            array(
                "msg" => "fail",
                "result" => null
            )
        )
    );
}

die(
    json_encode(
        array(
            "msg" => "success",
            "result" => array_map(function ($code, $unit) {
                return $code . "_" . $unit;},$subcode,$subunit)
        )
    )
);

 

이 코드를 실행하면 저는 다음과 같은 값이 출력되는것을 기대했습니다.

하지만 현실은 이러한 결과가 나옵니다.

?? PRODUCTCODE1의 정보가 나오지 않습니다.

원인은 아까 자바스크립트 코드에서 AJAX 요청 후 done 메소드의 콜백을 봐야합니다.

new Promise((resolve,reject) => {
    for(var key in product_info){
        $.ajax({
            url : "./Backend.php",
            type : "POST",
            dataType : "json",
            cache : false,
            data : {
                'prdcode' : key,
                'subcode' : product_info[key]['subcode'],
                'subunit' : product_info[key]['subunit'],
            }
        })
        .done(function(result){
            product_merge_info[key] = result['result']
        })
        .fail(function(result){
            console.log(result)
        })
    }
    resolve(product_merge_info)
})

done 메소드 안에 있는 콜백함수를 보시면

product_merge_info[key] 에서 key는 콜백함수 내에 있는 변수가 아닌 for문 내에 선언된 변수입니다.

for loop가 도는 동안 당연히 각각의 key 변수값이 product_merge_info 객체 의 key로 들어갈 것 같았지만 그렇지 않습니다. 정확한 이유는 조금 뒤에 설명하고 var 키워드에 대한 설명이 조금 필요합니다.

 

var 키워드로 선언된 변수는 같은 함수 내에 있는 동안에는 해당 변수가 공유가 됩니다.

아까 자바스크립트 코드를 다음과 같이 수정해봅시다

new Promise((resolve,reject) => {
    for(var key in product_info){
        $.ajax({
            url : "./Backend.php",
            type : "POST",
            dataType : "json",
            cache : false,
            data : {
                'prdcode' : key,
                'subcode' : product_info[key]['subcode'],
                'subunit' : product_info[key]['subunit'],
            }
        })
        .done(function(result){
            product_merge_info[key] = result['result']
        })
        .fail(function(result){
            console.log(result)
        })
    }
    resolve(product_merge_info)
    console.log(key)	// !! 추가된 부분 !!
}).then(() => {
    console.log(product_merge_info);
})

추가된 부분에서 key변수를 console에 출력합니다.

for문 내에서 선언한 변수인데도 불구하고 for문 밖에서도 key변수에 접근할 수 있습니다.

이처럼 var로 선언된 변수는 변수가 선언된 함수영역 내에서는 해당 변수에 접근할 수 있습니다.

 

AJAX done 메소드의 콜백함수가 실행될때에는 이미 for문은 종료가 되어있으므로

key값은 PRODUCTCODE2로 되어있습니다. 이 상황에서 product_merge_info[key]는

product_merge_info['PRODUCTCODE2']가 되므로 product_merge_info 객체의 키 값은 PRODUCTCODE2만 존재하게 됩니다.

 

for문을 도는 당시에 AJAX done 메소드의 콜백 함수에 product_merge_info[key]는 product_merge_info['PRODUCTCODE1'], product_merge_info['PRODUCTCODE2']이 들어가는것이 아니고 key 변수를 참조할 것이라는것을 알려줍니다.

 

위 예제 코드에서 key변수는 var키워드로 선언되어 있으므로 루프를 몇번을 돌던지 결국 한개의 var 키워드로 선언된 key를 찾게됩니다.

 

그러면 이러한 문제는 어떻게 해결 할 수 있을까요?

 

방법은 크게 두가지가 있습니다.

1. ES6 에서 추가된 let,const 키워드 사용

2. 즉시 실행 함수 사용

 

1번 방법은 간단합니다. 위의 for문에서 선언한 key 변수의 키워드를 var 대신 let나 const로 바꾸면 됩니다.

var product_info = {
    "PRODUCTCODE1" : {
        "subcode" : ["subcode11","subcode12"],
        "subunit" : ["subunit12","subunit12"],
    },
    "PRODUCTCODE2" : {
        "subcode" : ["subcode21","subcode22"],
        "subunit" : ["subunit21","subunit22"],
    },
}

var product_merge_info = {}

new Promise((resolve,reject) => {
    for(let key in product_info){	// !! 변경된 부분 !!
        $.ajax({
            url : "./Backend.php",
            type : "POST",
            dataType : "json",
            cache : false,
            data : {
                'prdcode' : key,
                'subcode' : product_info[key]['subcode'],
                'subunit' : product_info[key]['subunit'],
            }
        })
        .done(function(result){
            product_merge_info[key] = result['result']
        })
        .fail(function(result){
            console.log(result)
        })
    }
    resolve(product_merge_info)
}).then(() => {
    console.log(product_merge_info);
})

이제 원하던 결과가 나옵니다.

 

이제 두번째 방법을 설명합니다.

new Promise((resolve,reject) => {
    for(var key in product_info){
        (function(key2){
            $.ajax({
                url : "./test221213ajax.php",
                type : "POST",
                dataType : "json",
                cache : false,
                data : {
                    'prdcode' : key2,
                    'subcode' : product_info[key2]['subcode'],
                    'subunit' : product_info[key2]['subunit'],
                }
            })
            .done(function(result){
                product_merge_info[key2] = result['result']
            })
            .fail(function(result){
                console.log(result)
            })
        })(key);
    }
    resolve(product_merge_info)
}).then(() => {
    console.log(product_merge_info);
})

for문 다음 부분을 보면 익명함수가 괄호로 감싸져있습니다.

이를 즉시실행함수 라고 하는데 이 부분에 대한 설명은 여기서 확인하실 수 있습니다.

https://developer.mozilla.org/ko/docs/Glossary/IIFE

 

IIFE - MDN Web Docs 용어 사전: 웹 용어 정의 | MDN

**즉시 실행 함수 표현(IIFE, Immediately Invoked Function Expression)**은 정의되자마자 즉시 실행되는 Javascript Function 를 말한다.

developer.mozilla.org

 

2번 방법대로 코드를 수정하면 for loop가 돌면서 product_merge_info['PRODUCTCODE1'], product_merge_info['PRODUCTCODE2']이 되어 코드가 진행됩니다.

 

저의 경우에서는 ES6 문법을 사용하면 안되는 상황이었기 때문에 2번 방법으로 문제를 해결하였습니다.