Как избежать глобальных переменных в JavaScript? - PullRequest
72 голосов
/ 03 декабря 2009

Мы все знаем, что глобальные переменные - это далеко не лучшая практика. Но есть несколько случаев, когда трудно без них писать код. Какие методы вы используете, чтобы избежать использования глобальных переменных?

Например, учитывая следующий сценарий, как бы вы не использовали глобальную переменную?

Код JavaScript:

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">. Он загружает файлы по одному, чтобы предотвратить огромные запросы. Он делает это путем POST передачи в iframe, ожидая ответа, который запускает нагрузку iframe, а затем запускает следующую отправку.

Вам не нужно конкретно отвечать на этот пример, я просто предоставляю его для ссылки на ситуацию, в которой я не могу придумать, как избежать глобальных переменных.

Ответы [ 10 ]

64 голосов
/ 03 декабря 2009

Самый простой способ - обернуть ваш код в замыкание и вручную выставить только те переменные, которые вам нужны глобально, в глобальную область:

(function() {
    // Your code here

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

Чтобы ответить на комментарий Crescent Fresh: чтобы полностью исключить глобальные переменные из сценария, разработчику необходимо изменить ряд вещей, предполагаемых в вопросе. Это будет выглядеть примерно так:

Javascript:

(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.

55 голосов
/ 03 декабря 2009

Я предлагаю шаблон модуля .

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
7 голосов
/ 03 декабря 2009

Во-первых, невозможно избежать глобального 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 (где вы можете использовать шаблон модуля, если он вам нравится).

4 голосов
/ 03 декабря 2009
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);  
    }       
}
4 голосов
/ 03 декабря 2009

Иногда имеет смысл иметь глобальные переменные в JavaScript. Но не оставляйте их висящими прямо за окном.

Вместо этого создайте единый объект «пространство имен» для хранения глобальных переменных. Чтобы получить бонусные баллы, положите все, включая ваши методы.

2 голосов
/ 06 октября 2017

Другой способ сделать это - создать объект, а затем добавить к нему методы.

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

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

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

Таким образом, предоставляется только объект obj и прикрепленные к нему методы. Это эквивалентно добавлению его в пространство имен.

1 голос
/ 06 апреля 2011

Использование замыканий может быть приемлемым для небольших и средних проектов. Однако для больших проектов вы можете разделить код на модули и сохранить их в разных файлах.

Поэтому я написал плагин jQuery Secret для решения проблемы.

В вашем случае с этим плагином код будет выглядеть примерно так:

JavaScript:

// 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 , вы вообще не найдете глобалов, даже функций:)

Для полной документации, пожалуйста, смотрите здесь .

Демонстрационную страницу см. this .

Исходный код на GitHub .

1 голос
/ 03 декабря 2009

Некоторые вещи будут находиться в глобальном пространстве имен, а именно, какую бы функцию вы ни вызывали из встроенного кода JavaScript.

В общем, решение состоит в том, чтобы обернуть все в замыкание:

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

и избегайте встроенного обработчика.

0 голосов
/ 14 сентября 2016

Для "защиты" отдельных глобальных переменных:

function gInitUploadCount() {
    var uploadCount = 0;

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

gInitUploadCount();
gAddUploadCount();

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

Я новичок в JS, в настоящее время использую это в одном проекте. (Я ценю любые комментарии и критику)

0 голосов
/ 04 декабря 2009

Используйте замыкания. Нечто подобное дает вам область, отличную от глобальной.

(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
})();
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...