Làm cách nào để tránh các biến toàn cục trong JavaScript?


84

Tất cả chúng ta đều biết rằng các biến toàn cục là bất cứ điều gì ngoại trừ phương pháp hay nhất. Nhưng có một số trường hợp rất khó viết mã nếu không có chúng. Bạn sử dụng kỹ thuật nào để tránh việc sử dụng các biến toàn cục?

Ví dụ, với tình huống sau, bạn sẽ không sử dụng biến toàn cục như thế nào?

Mã 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();
}

Đánh dấu có liên quan:

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

Mã này đến từ một biểu mẫu web có nhiều <input type="file">. Nó tải lên từng tệp một để ngăn chặn các yêu cầu lớn. Nó thực hiện điều này bằng cách ĐĂNG nhập vào iframe, đợi phản hồi kích hoạt tải iframe và sau đó kích hoạt gửi tiếp theo.

Bạn không cần phải trả lời ví dụ này một cách cụ thể, tôi chỉ cung cấp nó để tham khảo cho một tình huống mà tôi không thể nghĩ ra cách để tránh các biến toàn cục.


3
Sử dụng Ngay Được triệu gọi biểu Function (IIFE) Bạn có thể đọc thêm ở đây: codearsenal.net/2014/11/...

Câu trả lời:


69

Cách dễ nhất là bọc mã của bạn trong một vùng đóng và chỉ hiển thị theo cách thủ công những biến bạn cần trên phạm vi toàn cầu:

(function() {
    // Your code here

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

Để giải quyết nhận xét của Crescent Fresh: để loại bỏ hoàn toàn các biến toàn cục khỏi kịch bản, nhà phát triển cần phải thay đổi một số điều được giả định trong câu hỏi. Nó sẽ trông giống như thế này hơn:

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>

Bạn không cần một trình xử lý sự kiện nội tuyến <iframe>, nó vẫn sẽ kích hoạt trên mỗi lần tải với mã này.

Về sự kiện tải

Đây là một trường hợp thử nghiệm chứng minh rằng bạn không cần một onloadsự kiện nội tuyến . Điều này phụ thuộc vào việc tham chiếu một tệp (/emptypage.php) trên cùng một máy chủ, nếu không, bạn có thể chỉ cần dán tệp này vào một trang và chạy nó.

<!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>

Cảnh báo kích hoạt mỗi khi tôi nhấp vào nút gửi trong Safari, Firefox, IE 6, 7 và 8.


Hoặc cung cấp một số loại công cụ truy cập. Tôi đồng ý.
Upperstage

3
Sẽ rất hữu ích khi mọi người bỏ phiếu cho họ để giải thích lý do họ bỏ phiếu.
mí mắt

6
Tôi đã không bỏ phiếu. Tuy nhiên, nói window ['varName'] = varName cũng giống như việc khai báo var toàn cục bên ngoài bao đóng. var foo = "bar"; (function() { alert(window['foo']) })();
Josh Stodola

Bạn đã trả lời tiêu đề, không phải câu hỏi. Tôi không thích điều đó. Đặt thành ngữ đóng trong ngữ cảnh của các tham chiếu từ trình xử lý sự kiện nội tuyến (vì phần thịt của câu hỏi đang diễn ra) sẽ tốt hơn.
Crescent Fresh

1
Crescent Fresh, tôi đã trả lời câu hỏi. Các giả định của câu hỏi sẽ cần được thiết kế lại để tránh các chức năng toàn cục. Câu trả lời này lấy các giả định của câu hỏi (ví dụ: một trình xử lý sự kiện nội tuyến) và cho phép nhà phát triển chỉ chọn các điểm truy cập toàn cầu cần thiết thay vì mọi thứ nằm trong phạm vi toàn cầu.
mí mắt

59

Tôi đề nghị mô-đun mô-đun .

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
Tôi sẽ +1 vì tôi hiểu điều này và nó cực kỳ hữu ích, nhưng tôi vẫn chưa rõ về mức độ hiệu quả của điều này trong trường hợp tôi chỉ sử dụng một biến toàn cục. Hãy sửa cho tôi nếu tôi sai, nhưng việc thực thi hàm này và trả về nó khiến đối tượng được trả về được lưu trữ trong YAHOO.myProject.myModuleđó, là một biến toàn cục. Đúng?
Josh Stodola

11
@Josh: biến toàn cầu không phải là xấu. Biến_S_ toàn cục là ác. Giữ số lượng hình cầu càng ít càng tốt.
ernon

Toàn bộ hàm ẩn danh sẽ được thực thi mỗi khi bạn muốn truy cập vào một trong các thuộc tính / phương thức công khai của 'mô-đun' phải không?
UpTheCreek

@UpTheCreek: không, sẽ không. Nó chỉ được thực thi một lần, khi chương trình gặp phải close () trên dòng cuối cùng và đối tượng trả về sẽ được gán cho thuộc tính myModule với bao đóng chứa myPrivateVar và myPrivateMethod.
ernon

Đẹp, chính xác là những gì tôi đang tìm kiếm. Điều này cho phép tôi tách logic trang của mình khỏi xử lý sự kiện jquery của tôi. Một câu hỏi đặt ra, với một cuộc tấn công XSS, kẻ tấn công sẽ vẫn có quyền truy cập vào YAHOO.myProject.myModule đúng không? Sẽ không tốt hơn nếu chỉ để hàm bên ngoài không tên và đặt một (); ở cuối, xung quanh $ (tài liệu) .ready? Có thể sửa đổi đối tượng meta của thuộc tính YAHOO.myProct.myModule? Tôi vừa đầu tư khá nhiều thời gian vào lý thuyết js và hiện đang cố gắng kết hợp nó với nhau.
Dale

8

Trước hết, không thể tránh được toàn cầu JavaScript, một thứ gì đó sẽ luôn nằm trong phạm vi toàn cầu. Ngay cả khi bạn tạo một không gian tên, vẫn là một ý tưởng hay, không gian tên đó sẽ là toàn cầu.

Tuy nhiên, có nhiều cách tiếp cận để không lạm dụng phạm vi toàn cầu. Hai trong số các cách đơn giản nhất là sử dụng hàm bao đóng, hoặc vì bạn chỉ có một biến cần theo dõi, chỉ cần đặt nó làm thuộc tính của chính hàm (sau đó có thể được coi là một staticbiến).

Khép kín

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();
  };
})();

* Lưu ý rằng sự gia tăng của uploadCountđang diễn ra nội bộ tại đây

Thuộc tính chức năng

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();
};

Tôi không chắc tại sao lại uploadCount++; if(uploadCount > 1) ...cần, vì có vẻ như điều kiện sẽ luôn đúng. Nhưng nếu bạn cần truy cập toàn cục vào biến, thì phương thức thuộc tính hàm mà tôi đã mô tả ở trên sẽ cho phép bạn làm như vậy mà biến thực sự không phải là toàn cục.

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

Tuy nhiên, nếu đúng như vậy, thì bạn có thể nên sử dụng một đối tượng theo nghĩa đen hoặc đối tượng được khởi tạo và thực hiện điều này theo cách OO thông thường (nơi bạn có thể sử dụng mô-đun nếu nó phù hợp với bạn).


1
Bạn hoàn toàn có thể tránh phạm vi toàn cầu. Trong ví dụ 'Đóng cửa' của bạn, chỉ cần xóa 'var startUpload =' ngay từ đầu, và chức năng đó sẽ được đóng hoàn toàn, không có khả năng truy cập ở cấp độ toàn cầu. Trong thực tế, nhiều người thích để lộ một biến duy nhất, bên trong có chứa tham chiếu đến mọi thứ khác
derrylwc

1
@derrylwc Trong trường hợp này, startUploadđó có phải là biến duy nhất mà bạn đang đề cập đến không. Loại bỏ var startUpload = khỏi ví dụ đóng có nghĩa là hàm bên trong sẽ không bao giờ có thể được thực thi vì không có tham chiếu đến nó. Vấn đề tránh ô nhiễm phạm vi toàn cầu liên quan đến biến bộ đếm bên trong, uploadCountđược sử dụng bởi startUpload. Hơn nữa, tôi nghĩ rằng OP đang cố gắng tránh làm ô nhiễm bất kỳ phạm vi nào bên ngoài phương pháp này với uploadCountbiến được sử dụng nội bộ .
Justin Johnson,

Nếu mã trong vùng đóng ẩn danh thêm người tải lên làm trình xử lý sự kiện, tất nhiên nó "sẽ có thể được thực thi" bất cứ khi nào sự kiện thích hợp xảy ra.
Damian Yerrick

6

Đôi khi việc có các biến toàn cục trong JavaScript là rất hợp lý. Nhưng đừng để chúng treo thẳng ngoài cửa sổ như vậy.

Thay vào đó, hãy tạo một đối tượng "không gian tên" để chứa hình cầu của bạn. Để có điểm thưởng, hãy đặt mọi thứ vào đó, bao gồm cả phương pháp của bạn.


Làm thế nào tôi có thể làm điều đó? tạo một đối tượng không gian tên duy nhất để chứa toàn cầu của tôi?
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);  
    }       
}

Làm cách nào để tăng uploadCount bên trong onloadtrình xử lý của tôi trên iframe? Đây là điều tối quan trọng.
Josh Stodola

1
OK, tôi hiểu bạn đã làm gì ở đây. Thật không may là điều này không giống nhau. Điều này kích hoạt tất cả các video tải lên riêng biệt, nhưng đồng thời (về mặt kỹ thuật, có 100ms ở giữa chúng). Giải pháp hiện tại tải chúng lên theo tuần tự, có nghĩa là lần tải lên thứ hai không bắt đầu cho đến khi lần tải lên đầu tiên hoàn thành. Đó là lý do tại sao onloadcần có trình xử lý nội tuyến . Việc gán trình xử lý theo chương trình không hoạt động vì nó chỉ kích hoạt lần đầu tiên. Trình xử lý nội tuyến kích hoạt mọi lúc (vì bất kỳ lý do gì).
Josh Stodola

Tôi vẫn đi 1 dù, bởi vì tôi thấy rằng đây là một phương pháp hiệu quả cất giấu một biến toàn cầu
Josh Stodola

Bạn có thể tạo iframe cho mỗi lần tải lên để duy trì các lệnh gọi lại riêng lẻ.
Justin Johnson

1
Tôi nghĩ rằng đây cuối cùng là một câu trả lời tuyệt vời, chỉ hơi bị che khuất bởi các yêu cầu của ví dụ cụ thể. Về cơ bản, ý tưởng là tạo một mẫu (đối tượng) và sử dụng 'mới' để khởi tạo nó. Đây có lẽ là câu trả lời tốt nhất bởi vì nó thực sự tránh một biến tức là toàn cầu mà không cần thỏa hiệp
PandaWood

3

Cách khác để làm điều này là tạo một đối tượng và sau đó thêm các phương thức vào nó.

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

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

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

Bằng cách này, chỉ có đối tượng 'obj' được hiển thị và các phương thức gắn liền với nó. Nó tương đương với việc thêm nó vào không gian tên.


2

Một số thứ sẽ nằm trong không gian tên chung - cụ thể là bất kỳ hàm nào bạn đang gọi từ mã JavaScript nội tuyến của mình.

Nói chung, giải pháp là đóng tất cả mọi thứ lại:

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

và tránh trình xử lý nội tuyến.


Trình xử lý nội tuyến được yêu cầu trong tình huống này. Khi biểu mẫu gửi đến iframe, bộ xử lý onload của bạn được lập trình sẽ không kích hoạt.
Josh Stodola

1
@Josh: thật à? iframe.onload = ...không tương đương với <iframe onload="..."?
Crescent Fresh

2

Sử dụng bao đóng có thể phù hợp cho các dự án vừa và nhỏ. Tuy nhiên, đối với các dự án lớn, bạn có thể muốn chia mã của mình thành các mô-đun và lưu chúng trong các tệp khác nhau.

Do đó tôi đã viết plugin jQuery Secret để giải quyết vấn đề.

Trong trường hợp của bạn với plugin này, mã sẽ giống như sau.

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;
    }
}

Đánh dấu có liên quan:

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

Mở Firebug của bạn , bạn sẽ không tìm thấy hình cầu nào, thậm chí không phải là funciton :)

Để có tài liệu đầy đủ, vui lòng xem tại đây .

Để có trang demo, vui lòng xem phần này .

Mã nguồn trên GitHub .


1

Sử dụng các đóng cửa. Một cái gì đó như thế này cung cấp cho bạn một phạm vi khác với toàn cầu.

(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
})();

Cách tiếp cận của tôi trước đây là xác định một đối tượng biến toàn cục cụ thể và gắn tất cả các biến toàn cục vào đó. Làm thế nào tôi sẽ kết thúc điều này trong một đóng cửa? Đáng buồn thay, giới hạn hộp nhận xét không cho phép tôi nhúng mã điển hình.
David Edwards

1

Để "bảo mật" các biến toàn cục trực quan:

function gInitUploadCount() {
    var uploadCount = 0;

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

gInitUploadCount();
gAddUploadCount();

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

Tôi là người mới làm quen với JS, hiện đang sử dụng cái này trong một dự án. (Tôi đánh giá cao bất kỳ nhận xét và chỉ trích nào)


1

Tôi sử dụng nó theo cách này:

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

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

    localFunc();
}

Đối với tất cả phạm vi toàn cục sử dụng 'var' và đối với phạm vi cục bộ sử dụng 'let'.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.