JavaScript에서 전역 변수를 피하는 방법은 무엇입니까?


84

우리 모두는 전역 변수 가 모범 사례가 아니라는 것을 알고 있습니다. 그러나 그것들 없이는 코딩하기 어려운 경우가 몇 가지 있습니다. 전역 변수 사용을 피하기 위해 어떤 기술을 사용합니까?

예를 들어 다음 시나리오에서 전역 변수를 사용하지 않는 방법은 무엇입니까?

자바 스크립트 코드 :

var uploadCount = 0;

window.onload = function() {
    var frm = document.forms[0];

    frm.target = "postMe";
    frm.onsubmit = function() {
        startUpload();
        return false;
    }
}

function startUpload() {
    var fil = document.getElementById("FileUpload" + uploadCount);

    if (!fil || fil.value.length == 0) {
        alert("Finished!");
        document.forms[0].reset();
        return;
    }

    disableAllFileInputs();
    fil.disabled = false;
    alert("Uploading file " + uploadCount);
    document.forms[0].submit();
}

관련 마크 업 :

<iframe src="test.htm" name="postHere" id="postHere"
  onload="uploadCount++; if(uploadCount > 1) startUpload();"></iframe>

<!-- MUST use inline JavaScript here for onload event
     to fire after each form submission. -->

이 코드는 여러 <input type="file">. 대규모 요청을 방지하기 위해 한 번에 하나씩 파일을 업로드합니다. iframe 에 POST 를 수행하고 iframe onload를 실행하는 응답을 기다린 다음 다음 제출을 트리거합니다.

이 예제에 구체적으로 대답 할 필요는 없습니다. 전역 변수를 피할 수있는 방법을 생각할 수없는 상황을 참조하기 위해 제공하는 것입니다.


3
IIFE (Immediately Invoked Function Expression) 사용 자세한 내용은 여기에서 읽을 수 있습니다. codearsenal.net/2014/11/…

답변:


69

가장 쉬운 방법은 코드를 클로저로 래핑하고 전역 적으로 필요한 변수 만 전역 범위에 수동으로 노출하는 것입니다.

(function() {
    // Your code here

    // Expose to global
    window['varName'] = varName;
})();

Crescent Fresh의 의견을 해결하기 위해 : 시나리오에서 전역 변수를 완전히 제거하기 위해 개발자는 질문에서 가정 한 여러 가지를 변경해야합니다. 다음과 같이 보일 것입니다.

자바 스크립트 :

(function() {
    var addEvent = function(element, type, method) {
        if('addEventListener' in element) {
            element.addEventListener(type, method, false);
        } else if('attachEvent' in element) {
            element.attachEvent('on' + type, method);

        // If addEventListener and attachEvent are both unavailable,
        // use inline events. This should never happen.
        } else if('on' + type in element) {
            // If a previous inline event exists, preserve it. This isn't
            // tested, it may eat your baby
            var oldMethod = element['on' + type],
                newMethod = function(e) {
                    oldMethod(e);
                    newMethod(e);
                };
        } else {
            element['on' + type] = method;
        }
    },
        uploadCount = 0,
        startUpload = function() {
            var fil = document.getElementById("FileUpload" + uploadCount);

            if(!fil || fil.value.length == 0) {    
                alert("Finished!");
                document.forms[0].reset();
                return;
            }

            disableAllFileInputs();
            fil.disabled = false;
            alert("Uploading file " + uploadCount);
            document.forms[0].submit();
        };

    addEvent(window, 'load', function() {
        var frm = document.forms[0];

        frm.target = "postMe";
        addEvent(frm, 'submit', function() {
            startUpload();
            return false;
        });
    });

    var iframe = document.getElementById('postHere');
    addEvent(iframe, 'load', function() {
        uploadCount++;
        if(uploadCount > 1) {
            startUpload();
        }
    });

})();

HTML :

<iframe src="test.htm" name="postHere" id="postHere"></iframe>

에 인라인 이벤트 핸들러 가 필요 하지 않으며이 <iframe>코드를 사용하여 각로드에서 계속 실행됩니다.

로드 이벤트 관련

다음은 인라인 onload이벤트 가 필요하지 않음을 보여주는 테스트 사례 입니다. 이것은 동일한 서버에서 파일 (/emptypage.php)을 참조하는 것에 달려 있습니다. 그렇지 않으면 이것을 페이지에 붙여넣고 실행할 수 있어야합니다.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    <title>untitled</title>
</head>
<body>
    <script type="text/javascript" charset="utf-8">
        (function() {
            var addEvent = function(element, type, method) {
                if('addEventListener' in element) {
                    element.addEventListener(type, method, false);
                } else if('attachEvent' in element) {
                    element.attachEvent('on' + type, method);

                    // If addEventListener and attachEvent are both unavailable,
                    // use inline events. This should never happen.
                } else if('on' + type in element) {
                    // If a previous inline event exists, preserve it. This isn't
                    // tested, it may eat your baby
                    var oldMethod = element['on' + type],
                    newMethod = function(e) {
                        oldMethod(e);
                        newMethod(e);
                    };
                } else {
                    element['on' + type] = method;
                }
            };

            // Work around IE 6/7 bug where form submission targets
            // a new window instead of the iframe. SO suggestion here:
            // http://stackoverflow.com/q/875650
            var iframe;
            try {
                iframe = document.createElement('<iframe name="postHere">');
            } catch (e) {
                iframe = document.createElement('iframe');
                iframe.name = 'postHere';
            }

            iframe.name = 'postHere';
            iframe.id = 'postHere';
            iframe.src = '/emptypage.php';
            addEvent(iframe, 'load', function() {
                alert('iframe load');
            });

            document.body.appendChild(iframe);

            var form = document.createElement('form');
            form.target = 'postHere';
            form.action = '/emptypage.php';
            var submit = document.createElement('input');
            submit.type = 'submit';
            submit.value = 'Submit';

            form.appendChild(submit);

            document.body.appendChild(form);
        })();
    </script>
</body>
</html>

Safari, Firefox, IE 6, 7 및 8에서 제출 버튼을 클릭 할 때마다 경고가 발생합니다.


또는 일종의 접근자를 제공하십시오. 나는 동의한다.
Upperstage 2009

3
사람들이 투표를 거부하는 이유를 설명하기 위해 투표 할 때 유용합니다.
눈꺼풀 없음 2009

6
나는 반대표를 던지지 않았습니다. 그러나 window [ 'varName'] = varName이라고 말하는 것은 클로저 외부에서 전역 var 선언을 만드는 것과 같습니다. var foo = "bar"; (function() { alert(window['foo']) })();
Josh Stodola

질문이 아니라 제목에 답하셨습니다. 나는 그것을 좋아하지 않았다. 인라인 이벤트 핸들러의 참조 컨텍스트 내에 클로저 관용구를 넣는 것이 더 좋을 것입니다.
Crescent Fresh

1
Crescent Fresh, 나는 질문에 답했다. 글로벌 함수를 피하기 위해 질문의 가정을 다시 설계해야합니다. 이 답변은 질문의 가정 (예 : 인라인 이벤트 처리기)을 지침으로 사용하여 개발자가 모든 것이 전역 범위에있는 것이 아니라 필요한 전역 액세스 포인트 만 선택할 수 있도록합니다.
눈꺼풀 없음 2009

59

나는 모듈 패턴을 제안한다 .

YAHOO.myProject.myModule = function () {

    //"private" variables:
    var myPrivateVar = "I can be accessed only from within YAHOO.myProject.myModule.";

    //"private" method:
    var myPrivateMethod = function () {
        YAHOO.log("I can be accessed only from within YAHOO.myProject.myModule");
    }

    return  {
        myPublicProperty: "I'm accessible as YAHOO.myProject.myModule.myPublicProperty."
        myPublicMethod: function () {
            YAHOO.log("I'm accessible as YAHOO.myProject.myModule.myPublicMethod.");

            //Within myProject, I can access "private" vars and methods:
            YAHOO.log(myPrivateVar);
            YAHOO.log(myPrivateMethod());

            //The native scope of myPublicMethod is myProject; we can
            //access public members using "this":
            YAHOO.log(this.myPublicProperty);
        }
    };

}(); // the parens here cause the anonymous function to execute and return

3
나는 이것을 이해하고 매우 도움이되기 때문에 +1 할 것입니다. 그러나 이것이 하나의 전역 변수 만 사용하는 상황에서 이것이 얼마나 효과적 일지 여전히 확실하지 않습니다. 내가 틀렸다면 정정하십시오.하지만이 함수를 실행하고 반환하면 반환 된 객체 YAHOO.myProject.myModule가 전역 변수 인에 저장됩니다 . 권리?
Josh Stodola

11
@Josh : 전역 변수는 악하지 않습니다. 전역 변수 _S_는 악합니다. 전역 수를 가능한 한 적게 유지하십시오.
erenon 2009

전체 익명 함수는 '모듈'공용 속성 / 방법 중 하나에 액세스하려고 할 때마다 실행됩니다. 맞습니까?
UpTheCreek 2011-09-05

@UpTheCreek : 아니요, 그렇지 않습니다. 프로그램이 마지막 줄에서 닫는 ()을 만나면 한 번만 실행되고 반환 된 개체는 myPrivateVar 및 myPrivateMethod를 포함하는 클로저를 사용하여 myModule 속성에 할당됩니다.
erenon 2011 년

아름답고 정확히 내가 찾고 있던 것입니다. 이를 통해 내 페이지 로직을 내 jquery 이벤트 처리와 분리 할 수 ​​있습니다. XSS 공격으로 공격자가 YAHOO.myProject.myModule에 계속 액세스 할 수있는 질문입니다. 맞습니까? 외부 함수를 이름없이 그대로두고 ()를 넣는 것이 낫지 않을까요? 마지막에 $ (document) .ready? YAHOO.myProct.myModule 속성의 메타 개체를 수정할 수 있습니까? 나는 js 이론에 꽤 많은 시간을 투자했고 지금 그것을 맞추려고 노력하고 있습니다.
Dale

8

첫째, 전역 JavaScript를 피하는 것은 불가능합니다. 무언가는 항상 전역 범위에 매달려 있습니다. 여전히 좋은 생각 인 네임 스페이스를 생성하더라도 해당 네임 스페이스는 전역이됩니다.

그러나 글로벌 범위를 남용하지 않는 방법은 많습니다. 가장 간단한 방법 중 두 가지는 클로저를 사용하거나 추적해야 할 변수가 하나뿐이므로 함수 자체의 속성으로 설정하기 만하면됩니다 (그러면 static변수 로 처리 될 수 있음 ).

폐쇄

var startUpload = (function() {
  var uploadCount = 1;  // <----
  return function() {
    var fil = document.getElementById("FileUpload" + uploadCount++);  // <----

    if(!fil || fil.value.length == 0) {    
      alert("Finished!");
      document.forms[0].reset();
      uploadCount = 1; // <----
      return;
    }

    disableAllFileInputs();
    fil.disabled = false;
    alert("Uploading file " + uploadCount);
    document.forms[0].submit();
  };
})();

* 증가는 uploadCount여기서 내부적으로 발생합니다.

기능 속성

var startUpload = function() {
  startUpload.uploadCount = startUpload.count || 1; // <----
  var fil = document.getElementById("FileUpload" + startUpload.count++);

  if(!fil || fil.value.length == 0) {    
    alert("Finished!");
    document.forms[0].reset();
    startUpload.count = 1; // <----
    return;
  }

  disableAllFileInputs();
  fil.disabled = false;
  alert("Uploading file " + startUpload.count);
  document.forms[0].submit();
};

uploadCount++; if(uploadCount > 1) ...조건이 항상 참인 것처럼 보이기 때문에 왜 필요한지 잘 모르겠습니다 . 그러나 변수에 대한 전역 액세스가 필요한 경우 위에서 설명한 함수 속성 메서드를 사용하면 변수가 실제로 전역이되지 않고도 그렇게 할 수 있습니다.

<iframe src="test.htm" name="postHere" id="postHere"
  onload="startUpload.count++; if (startUpload.count > 1) startUpload();"></iframe>

그러나 그럴 경우에는 아마도 객체 리터럴 또는 인스턴스화 된 객체를 사용하고 일반적인 OO 방식으로 진행해야합니다 (원하는 경우 모듈 패턴을 사용할 수 있음).


1
절대적으로 전역 범위를 피할 수 있습니다. 'Closure'예제에서 단순히 'var startUpload ='를 처음부터 제거하면 해당 함수가 전역 수준에서 액세스 가능성없이 완전히 포함됩니다. 실제로, 많은 사람들은 내 다른 모든 것들에 대한 참조 포함되어, 하나의 변수를 노출하는 것을 선호
derrylwc

1
@derrylwc이 경우 startUpload참조하는 단일 변수입니다. var startUpload = 클로저 예제에서 제거 한다는 것은 내부 함수에 대한 참조가 없기 때문에 실행될 수 없음을 의미합니다. 글로벌 범위 오염을 피하는 문제는에서 사용하는 내부 카운터 변수와 관련 uploadCountstartUpload있습니다. 또한 OP는 내부적으로 사용되는 uploadCount변수 로이 방법 외부의 범위를 오염시키지 않으려 고 노력하고 있다고 생각 합니다.
Justin Johnson

익명 클로저 내의 코드가 업 로더를 이벤트 리스너로 추가하면 당연히 적절한 이벤트가 발생할 때마다 "실행될 수 있습니다".
Damian Yerrick

6

때로는 JavaScript에 전역 변수를 사용하는 것이 합리적입니다. 하지만 창문에 직접 매달아 두지 마세요.

대신 전역을 포함 할 단일 "네임 스페이스"개체를 만듭니다. 보너스 포인트를 얻으려면 방법을 포함하여 모든 것을 거기에 넣으십시오.


어떻게 할 수 있습니까? 내 전역을 포함 할 단일 네임 스페이스 개체를 만드시겠습니까?
p.matsinopoulos

5
window.onload = function() {
  var frm = document.forms[0];
  frm.target = "postMe";
  frm.onsubmit = function() {
    frm.onsubmit = null;
    var uploader = new LazyFileUploader();
    uploader.startUpload();
    return false;
  }
}

function LazyFileUploader() {
    var uploadCount = 0;
    var total = 10;
    var prefix = "FileUpload";  
    var upload = function() {
        var fil = document.getElementById(prefix + uploadCount);

        if(!fil || fil.value.length == 0) {    
            alert("Finished!");
            document.forms[0].reset();
            return;
         }

        disableAllFileInputs();
        fil.disabled = false;
        alert("Uploading file " + uploadCount);
        document.forms[0].submit();
        uploadCount++;

        if (uploadCount < total) {
            setTimeout(function() {
                upload();
            }, 100); 
        }
    }

    this.startUpload = function() {
        setTimeout(function() {
            upload();
        }, 100);  
    }       
}

onloadiframe의 핸들러 내에서 uploadCount를 어떻게 증가 합니까? 이것은 매우 중요합니다.
Josh Stodola

1
네, 여기서 뭘했는지 봅니다. 불행히도 이것은 동일하지 않습니다. 이렇게하면 모든 업로드가 개별적으로 실행되지만 동시에 (기술적으로 그 사이에 100ms가 있습니다). 현재 솔루션은이를 순차적으로 업로드합니다. 즉, 첫 번째 업로드가 완료 될 때까지 두 번째 업로드가 시작되지 않습니다. 이것이 인라인 onload핸들러가 필요한 이유 입니다. 프로그래밍 방식으로 처리기를 할당하는 것은 처음에만 실행되기 때문에 작동하지 않습니다. 인라인 핸들러는 (어떤 이유로 든) 매번 실행됩니다.
Josh Stodola

그래도 +1을하려고합니다. 이것이 전역 변수를 숨기는 효과적인 방법이라는 것을 알기 때문입니다
Josh Stodola

개별 콜백을 유지하기 위해 업로드 할 때마다 iframe을 만들 수 있습니다.
Justin Johnson

1
나는 이것이 궁극적으로 훌륭한 대답이라고 생각하며 특정 예제의 요구 사항에 의해 약간 흐려집니다. 기본적으로 아이디어는 템플릿 (객체)을 만들고 'new'를 사용하여 인스턴스화하는 것입니다. 이것은 아마도 타협없이 전역 변수를 피하기 때문에 아마도 최선의 대답 일 것입니다
PandaWood

3

이를 수행하는 다른 방법은 개체를 만든 다음 여기에 메서드를 추가하는 것입니다.

var object = {
  a = 21,
  b = 51
};

object.displayA = function() {
 console.log(object.a);
};

object.displayB = function() {
 console.log(object.b);
};

이런 식으로 'obj'객체 만 노출되고 여기에 메서드가 첨부됩니다. 네임 스페이스에 추가하는 것과 같습니다.


2

어떤 것들은 전역 네임 스페이스에있을 것입니다. 즉, 인라인 JavaScript 코드에서 어떤 함수를 호출하든 말입니다.

일반적으로 해결책은 모든 것을 클로저로 감싸는 것입니다.

(function() {
    var uploadCount = 0;
    function startupload() {  ...  }
    document.getElementById('postHere').onload = function() {
        uploadCount ++;
        if (uploadCount > 1) startUpload();
    };
})();

인라인 핸들러를 피하십시오.


이 상황에서는 인라인 처리기가 필요합니다. 양식이 iframe에 제출되면 프로그래밍 방식으로 설정된 onload 핸들러가 실행되지 않습니다.
Josh Stodola

1
@Josh : 워, 정말? iframe.onload = ...와 동일하지 <iframe onload="..."않습니까?
Crescent Fresh

2

클로저를 사용하면 중소 규모 프로젝트에 적합 할 수 있습니다. 그러나 큰 프로젝트의 경우 코드를 모듈로 분할하고 다른 파일에 저장할 수 있습니다.

따라서 문제를 해결하기 위해 jQuery Secret 플러그인 을 작성했습니다 .

이 플러그인을 사용하는 경우 코드는 다음과 같습니다.

자바 스크립트 :

// Initialize uploadCount.
$.secret( 'in', 'uploadCount', 0 ).

// Store function disableAllFileInputs.
secret( 'in', 'disableAllFileInputs', function(){
  // Code for 'disable all file inputs' goes here.

// Store function startUpload
}).secret( 'in', 'startUpload', function(){
    // 'this' points to the private object in $.secret
    // where stores all the variables and functions
    // ex. uploadCount, disableAllFileInputs, startUpload.

    var fil = document.getElementById( 'FileUpload' + uploadCount);

    if(!fil || fil.value.length == 0) {
        alert( 'Finished!' );
        document.forms[0].reset();
        return;
    }

    // Use the stored disableAllFileInputs function
    // or you can use $.secret( 'call', 'disableAllFileInputs' );
    // it's the same thing.
    this.disableAllFileInputs();
    fil.disabled = false;

    // this.uploadCount is equal to $.secret( 'out', 'uploadCount' );
    alert( 'Uploading file ' + this.uploadCount );
    document.forms[0].submit();

// Store function iframeOnload
}).secret( 'in', 'iframeOnload', function(){
    this.uploadCount++;
    if( this.uploadCount > 1 ) this.startUpload();
});

window.onload = function() {
    var frm = document.forms[0];

    frm.target = "postMe";
    frm.onsubmit = function() {
        // Call out startUpload function onsubmit
        $.secret( 'call', 'startUpload' );
        return false;
    }
}

관련 마크 업 :

<iframe src="test.htm" name="postHere" id="postHere" onload="$.secret( 'call', 'iframeOnload' );"></iframe>

Firebug를 열면 전역이 전혀 없으며 funciton도 없습니다. :)

전체 문서는 여기 를 참조 하십시오 .

데모 페이지는 여기를 참조 하십시오 .

GitHub의 소스 코드 .


1

클로저를 사용하십시오. 이와 같은 것은 글로벌 이외의 범위를 제공합니다.

(function() {
    // Your code here
    var var1;
    function f1() {
        if(var1){...}
    }

    window.var_name = something; //<- if you have to have global var
    window.glob_func = function(){...} //<- ...or global function
})();

과거에 나의 접근 방식은 특정 전역 변수 개체를 정의하고 모든 전역 변수를 여기에 연결하는 것이 었습니다. 이것을 어떻게 마무리로 감쌀까요? 슬프게도 주석 상자 제한으로 인해 일반적인 코드를 포함 할 수 없습니다.
데이비드 에드워즈

1

개별 전역 변수를 "보안"하려면 :

function gInitUploadCount() {
    var uploadCount = 0;

    gGetUploadCount = function () {
        return uploadCount; 
    }
    gAddUploadCount= function () {
        uploadCount +=1;
    } 
}

gInitUploadCount();
gAddUploadCount();

console.log("Upload counter = "+gGetUploadCount());

나는 현재 하나의 프로젝트에서 이것을 사용하는 JS의 초보자입니다. (나는 어떤 논평과 비판도 가중시킨다)


1

이 방법으로 사용합니다.

{
    var globalA = 100;
    var globalB = 200;
    var globalFunc = function() { ... }

    let localA = 10;
    let localB = 20;
    let localFunc = function() { ... }

    localFunc();
}

모든 전역 범위에는 'var'를 사용하고 로컬 범위에는 'let'을 사용하십시오.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.