Ngạc nhiên rằng biến toàn cục có giá trị không xác định trong JavaScript


87

Hôm nay, tôi hoàn toàn ngạc nhiên khi thấy rằng một biến toàn cục có undefined giá trị trong một số trường hợp nhất định.

Thí dụ:

var value = 10;
function test() {
    //A
    console.log(value);
    var value = 20;

    //B
    console.log(value);
}
test();

Cung cấp đầu ra là

undefined
20

Ở đây, tại sao công cụ JavaScript lại coi giá trị toàn cầu là undefined . Tôi biết rằng JavaScript là một ngôn ngữ thông dịch. Làm thế nào nó có thể xem xét các biến trong hàm?

Đó có phải là một cạm bẫy từ công cụ JavaScript?

Câu trả lời:


175

Hiện tượng này được gọi là: JavaScript Variable Hoisting .

Tại thời điểm nào bạn không truy cập vào biến toàn cục trong hàm của bạn; bạn chỉ truy cập vào valuebiến cục bộ.

Mã của bạn tương đương với mã sau:

var value = 10;

function test() {
    var value;
    console.log(value);

    value = 20;
    console.log(value);
}

test();

Bạn vẫn ngạc nhiên khi nhận được undefined?


Giải trình:

Đây là điều mà mọi lập trình viên JavaScript sớm hay muộn đều gặp phải. Nói một cách đơn giản, bất kỳ biến nào bạn khai báo luôn được đưa lên đầu trang đóng cục bộ của bạn. Vì vậy, mặc dù bạn đã khai báo biến sau lần console.loggọi đầu tiên , nó vẫn được coi là bạn đã khai báo trước đó.
Tuy nhiên, chỉ có phần khai báo đang được kéo lên; mặt khác, không phải là nhiệm vụ.

Vì vậy, khi bạn gọi lần đầu tiên console.log(value), bạn đang tham chiếu đến biến được khai báo cục bộ của mình, biến này chưa được gán gì cho nó; do đó undefined.

Đây là một ví dụ khác :

var test = 'start';

function end() {
    test = 'end';
    var test = 'local';
}

end();
alert(test);

Bạn nghĩ điều này sẽ cảnh báo điều gì? Không, đừng chỉ đọc, hãy nghĩ về nó. Giá trị của là testgì?

Nếu bạn nói bất cứ điều gì khác hơn start, bạn đã sai. Đoạn mã trên tương đương với mã này:

var test = 'start';

function end() {
    var test;
    test = 'end';
    test = 'local';
}

end();
alert(test);

để biến toàn cục không bao giờ bị ảnh hưởng.

Như bạn có thể thấy, bất kể bạn đặt khai báo biến của mình ở đâu, nó luôn được đưa lên đầu trang đóng cục bộ của bạn.


Lưu ý phụ:

Điều này cũng áp dụng cho các chức năng.

Hãy xem xét đoạn mã này :

test("Won't work!");

test = function(text) { alert(text); }

điều này sẽ cung cấp cho bạn một lỗi tham chiếu:

Uncaught ReferenceError: test không được xác định

Điều này ném ra rất nhiều nhà phát triển, vì đoạn mã này hoạt động tốt:

test("Works!");

function test(text) { alert(text); }

Lý do cho điều này, như đã nói, là vì phần chuyển nhượng không được nâng lên. Vì vậy, trong ví dụ đầu tiên, khi test("Won't work!")được chạy, testbiến đã được khai báo, nhưng vẫn chưa được gán hàm cho nó.

Trong ví dụ thứ hai, chúng tôi không sử dụng phép gán biến. Thay vào đó, chúng tôi đang sử dụng cú pháp khai báo hàm thích hợp, cú pháp này sẽ đưa hàm hoàn toàn vào.


Ben Cherry đã viết một bài báo xuất sắc về vấn đề này, có tiêu đề thích hợp JavaScript Scoping and Hoisting .
Đọc nó. Nó sẽ cung cấp cho bạn toàn cảnh chi tiết.


27
Đây là một lời giải thích tốt. Tuy nhiên, tôi bỏ lỡ một giải pháp mà bạn có thể truy cập các biến toàn cục bên trong một hàm.
DuKes0mE

1
@ DuKes0mE - bạn luôn có thể truy cập các biến toàn cục bên trong một hàm.
Joseph Silber

3
có, và tại sao trường hợp A từ bài mở đầu không hoạt động và nói là không xác định của nó? Tôi hiểu, nó sẽ được hiểu là var cục bộ thay vì toàn cục, nhưng làm thế nào để nó trở thành toàn cầu?
DuKes0mE

4
để truy cập toàn cầu window.value sử dụng varaible
Venkat Reddy

1
khi nào thì kiểu treo biến này được triển khai? điều này đã luôn luôn là tiêu chuẩn trong javascript?
Dieskim

54

Tôi hơi thất vọng vì vấn đề ở đây được giải thích, nhưng không ai đề xuất giải pháp. Nếu bạn muốn truy cập một biến toàn cục trong phạm vi hàm mà không cần hàm tạo var cục bộ không xác định trước, hãy tham chiếu var dưới dạngwindow.varName


8
Yeah, thật tệ khi những người khác không đề xuất giải pháp vì đây là kết quả đầu tiên trong kết quả của Google. Họ đã làm gần như trả lời các câu hỏi thực tế được hỏi về nó là một cạm bẫy trong công cụ js .... thở dài -> cảm ơn vì câu trả lời của bạn
user1567453

2
Đó là sự khác biệt giữa kiến ​​thức lý thuyết và hoàn thành. Cảm ơn vì câu trả lời đó!
hansTheFranz

Đối với tôi, thật sai lầm khi tôi đặt tên toàn cục ở bất kỳ đâu trong bất kỳ hàm nào. Ít nhất cũng gây nhầm lẫn. Làm cho nhu cầu tìm kiếm google, tồi tệ nhất. Cảm ơn
dcromley

Javascript luôn làm tôi ngạc nhiên mỗi ngày trôi qua. Cảm ơn người đàn ông, câu trả lời là hữu ích.
Raf

Cảm ơn giải pháp, tôi đã tìm thấy giải pháp này sau khi var toàn cầu không được xác định nhưng chỉ trong Safari. Các tệp 'bao gồm' khác và các biến tổng thể hiển thị chẳng hạn như 'google', vì vậy tôi đã sao chép cách tiếp cận mà google đã sử dụng: window.globalVarFromJS = window.globalVarFromJS || {}; Sau đó, tôi tìm thấy giải pháp của bạn vì vậy tôi nghĩ rằng tôi sẽ thêm vào nó.
Ralph Hinkley

10

Các biến trong JavaScript luôn có phạm vi toàn hàm. Ngay cả khi chúng được xác định ở giữa hàm, chúng vẫn hiển thị trước đó. Các hiện tượng tương tự có thể được quan sát với vận thăng chức năng.

Điều đó đang được nói, cái đầu tiên console.log(value)nhìn thấy valuebiến (cái bên trong phủ bóng bên ngoài value), nhưng nó vẫn chưa được khởi tạo. Bạn có thể nghĩ về nó như thể tất cả các khai báo biến đã được chuyển ngầm đến đầu hàm ( không phải khối mã bên trong nhất), trong khi các định nghĩa được giữ nguyên ở cùng một vị trí.

Xem thêm


Tôi luôn yêu từ đơn giản 1 :)
Jashwant

3

Có một biến toàn cục value, nhưng khi điều khiển đi vào testhàm, một valuebiến khác được khai báo, biến này làm bóng biến toàn cục. Vì khai báo biến ( nhưng không phải phép gán ) trong JavaScript được đưa lên đầu phạm vi mà chúng được khai báo:

//value == undefined (global)
var value = 10;
//value == 10 (global)

function test() {
    //value == undefined (local)
    var value = 20;
    //value == 20 (local)
}
//value == 10 (global)

Lưu ý rằng điều này cũng đúng với các khai báo hàm, có nghĩa là bạn có thể gọi một hàm trước khi nó xuất hiện được xác định trong mã của bạn:

test(); //Call the function before it appears in the source
function test() {
    //Do stuff
}

Cũng cần lưu ý rằng khi bạn kết hợp cả hai thành một biểu thức hàm, biến sẽ có undefinedcho đến khi phép gán diễn ra, vì vậy bạn không thể gọi hàm cho đến khi điều đó xảy ra:

var test = function() {
    //Do stuff
};
test(); //Have to call the function after the assignment

0
  1. Tất nhiên, cách đơn giản nhất để giữ quyền truy cập vào các biến bên ngoài (không chỉ phạm vi toàn cục) là cố gắng không khai báo lại chúng dưới cùng một tên trong các hàm; chỉ cần không sử dụng var ở đó. Nên sử dụng các quy tắc đặt tên mô tả thích hợp . Với những thứ đó, sẽ khó kết thúc với các biến có tên như giá trị (khía cạnh này không nhất thiết phải liên quan đến ví dụ trong câu hỏi vì tên biến này có thể đã được đặt cho đơn giản).

  2. Nếu hàm có thể được sử dụng lại ở nơi khác và do đó không có gì đảm bảo rằng biến bên ngoài thực sự được xác định trong ngữ cảnh mới đó, thì hàm Eval có thể được sử dụng. Nó chậm trong hoạt động này vì vậy nó không được khuyến nghị cho các chức năng đòi hỏi hiệu suất:

    if (typeof variable === "undefined")
    {
        eval("var variable = 'Some value';");
    }
    
  3. Nếu biến phạm vi bên ngoài mà bạn muốn truy cập được xác định trong một hàm được đặt tên, thì nó có thể được gắn vào chính hàm ngay từ đầu và sau đó được truy cập từ bất kỳ đâu trong mã - có thể là từ các hàm lồng nhau sâu hoặc trình xử lý sự kiện bên ngoài mọi thứ khác. Lưu ý rằng việc truy cập vào các thuộc tính chậm hơn và sẽ yêu cầu bạn thay đổi cách lập trình, vì vậy, nó không được khuyến nghị trừ khi thực sự cần thiết: Các biến dưới dạng thuộc tính của hàm (JSFiddle) :

    // (the wrapper-binder is only necessary for using variables-properties
    // via "this"instead of the function's name)
    var functionAsImplicitObjectBody = function()
    {
        function someNestedFunction()
        {
            var redefinableVariable = "redefinableVariable's value from someNestedFunction";
            console.log('--> functionAsImplicitObjectBody.variableAsProperty: ', functionAsImplicitObjectBody.variableAsProperty);
            console.log('--> redefinableVariable: ', redefinableVariable);
        }
        var redefinableVariable = "redefinableVariable's value from someFunctionBody";
        console.log('this.variableAsProperty: ', this.variableAsProperty);
        console.log('functionAsImplicitObjectBody.variableAsProperty: ', functionAsImplicitObjectBody.variableAsProperty);
        console.log('redefinableVariable: ', redefinableVariable);
        someNestedFunction();
    },
    functionAsImplicitObject = functionAsImplicitObjectBody.bind(functionAsImplicitObjectBody);
    functionAsImplicitObjectBody.variableAsProperty = "variableAsProperty's value, set at time stamp: " + (new Date()).getTime();
    functionAsImplicitObject();
    
    // (spread-like operator "..." provides passing of any number of arguments to
    // the target internal "func" function in as many steps as necessary)
    var functionAsExplicitObject = function(...arguments)
    {
        var functionAsExplicitObjectBody = {
            variableAsProperty: "variableAsProperty's value",
            func: function(argument1, argument2)
            {
                function someNestedFunction()
                {
                    console.log('--> functionAsExplicitObjectBody.variableAsProperty: ',
                        functionAsExplicitObjectBody.variableAsProperty);
                }
                console.log("argument1: ", argument1);
                console.log("argument2: ", argument2);
                console.log("this.variableAsProperty: ", this.variableAsProperty);
                someNestedFunction();
            }    
        };
        return functionAsExplicitObjectBody.func(...arguments);
    };
    functionAsExplicitObject("argument1's value", "argument2's value");
    

0

Tôi đã gặp phải vấn đề tương tự ngay cả với các biến toàn cục. Vấn đề của tôi, tôi phát hiện ra, là biến toàn cục không tồn tại giữa các tệp html.

<script>
    window.myVar = 'foo';
    window.myVarTwo = 'bar';
</script>
<object type="text/html" data="/myDataSource.html"></object>

Tôi đã cố gắng tham chiếu myVar và myVarTwo trong tệp HTML đã tải, nhưng nhận được lỗi không xác định. Câu chuyện dài / ngày ngắn, tôi phát hiện ra rằng tôi có thể tham chiếu các biến bằng cách sử dụng:

<!DOCTYPE html>
<html lang="en">
    <!! other stuff here !!>
    <script>

        var myHTMLVar = this.parent.myVar

        /* other stuff here */
    </script>
</html>
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.