Sự khác biệt giữa các cú pháp khai báo biến trong Javascript (bao gồm các biến toàn cục)?


292

Có sự khác biệt nào giữa việc khai báo một biến không:

var a=0; //1

...cách này:

a=0; //2

...hoặc là:

window.a=0; //3

trong phạm vi toàn cầu?


2
AFAIK var a = 0; không hoạt động trong IE khi truy cập biến qua tệp js bên ngoài khác được khai báo trong tệp js khác
Aivan Monceller

Tôi không biết về window.a nhưng 2 cách khác giống nhau trong phạm vi toàn cầu.
lập trình viên

1
@AivanMonceller thực sự? xin vui lòng liên kết.
Raynos

@Raynos, tôi trải nghiệm nó trên trang web của riêng tôi. IE6 được cụ thể. Tôi không thể làm cho var enum của mình xuất hiện trên tệp js bên ngoài và tôi đang tham chiếu nó dưới dạng javascript nội tuyến trên tệp html
Aivan Monceller

@Ashwini Trong phạm vi toàn cầu, cửa sổ là đối tượng toàn cầu (trong trình duyệt). var a = 1; console.log (a); console.log (thắng
leebriggs

Câu trả lời:


557

Vâng, có một vài sự khác biệt, mặc dù về mặt thực tế, chúng thường không phải là lớn.

Có một cách thứ tư, và kể từ ES2015 (ES6) có thêm hai cách nữa. Tôi đã thêm cách thứ tư vào cuối, nhưng đã chèn các cách ES2015 sau # 1 (bạn sẽ thấy lý do tại sao), vì vậy chúng tôi có:

var a = 0;     // 1
let a = 0;     // 1.1 (new with ES2015)
const a = 0;   // 1.2 (new with ES2015)
a = 0;         // 2
window.a = 0;  // 3
this.a = 0;    // 4

Những tuyên bố đó đã giải thích

# 1 var a = 0;

Điều này tạo ra một biến toàn cục cũng là một thuộc tính của đối tượng toàn cầu , mà chúng ta truy cập như windowtrên các trình duyệt (hoặc thông qua thisphạm vi toàn cầu, trong mã không nghiêm ngặt). Không giống như một số tài sản khác, tài sản không thể được gỡ bỏ thông qua delete.

Theo thuật ngữ đặc tả, nó tạo ra một định danh ràng buộc trên Bản ghi môi trường đối tượng cho môi trường toàn cầu . Điều đó làm cho nó trở thành một tài sản của đối tượng toàn cầu bởi vì đối tượng toàn cầu là nơi các ràng buộc định danh cho đối tượng Môi trường toàn cầu được ghi lại. Đây là lý do tại sao tài sản không thể xóa được: Nó không chỉ là một thuộc tính đơn giản, nó là một ràng buộc định danh.

Liên kết (biến) được xác định trước khi dòng mã đầu tiên chạy (xem "Khi varxảy ra" bên dưới).

Lưu ý rằng trên IE8 trở về trước, thuộc tính được tạo trên windowkhông thể đếm được (không hiển thị trong các for..inbáo cáo). Trong IE9, Chrome, Firefox và Opera, nó là vô số.


# 1.1 let a = 0;

Điều này tạo ra một biến toàn cục không phải là một thuộc tính của đối tượng toàn cầu. Đây là một điều mới kể từ ES2015.

Trong các thuật ngữ đặc tả, nó tạo ra một định danh ràng buộc trên Bản ghi môi trường khai báo cho môi trường toàn cầu thay vì Bản ghi môi trường đối tượng . Môi trường toàn cầu độc đáo ở chỗ có một sự chia rẽ Môi trường Ghi, một cho tất cả những thứ cũ mà đi trên đối tượng toàn cầu (các đối tượng môi trường Record) và một cho tất cả các công cụ mới ( let, const, và các chức năng được tạo ra bởi class) mà không làm đi vào đối tượng toàn cầu.

Liên kết được tạo trước khi bất kỳ mã từng bước nào trong khối kèm theo của nó được thực thi (trong trường hợp này, trước khi bất kỳ mã toàn cầu nào chạy), nhưng nó không thể truy cập theo bất kỳ cách nào cho đến khi thực thi từng bước đạt được letcâu lệnh. Khi thực hiện đạt đến letcâu lệnh, biến có thể truy cập. (Xem phần "Khi nào letconstxảy ra" bên dưới.)


# 1.2 const a = 0;

Tạo một hằng số toàn cầu, không phải là một tài sản của đối tượng toàn cầu.

consthoàn toàn giống như letngoại trừ việc bạn phải cung cấp bộ khởi tạo ( = valuephần) và bạn không thể thay đổi giá trị của hằng số khi nó được tạo. Dưới vỏ bọc, nó chính xác như thế letnhưng với một cờ trên ràng buộc định danh cho biết giá trị của nó không thể thay đổi. Sử dụng constba điều cho bạn:

  1. Làm cho nó thành một lỗi thời gian phân tích nếu bạn cố gắng gán cho hằng số.
  2. Tài liệu bản chất không thay đổi của nó cho các lập trình viên khác.
  3. Cho phép công cụ JavaScript tối ưu hóa trên cơ sở nó sẽ không thay đổi.

# 2 a = 0;

Điều này tạo ra một tài sản trên đối tượng toàn cầu ngầm . Vì nó là một tài sản bình thường, bạn có thể xóa nó. Tôi khuyên bạn không nên làm điều này, nó có thể không rõ ràng với bất cứ ai đọc mã của bạn sau này. Nếu bạn sử dụng chế độ nghiêm ngặt của ES5, thực hiện việc này (gán cho biến không tồn tại) là một lỗi. Đó là một trong nhiều lý do để sử dụng chế độ nghiêm ngặt.

Và thật thú vị, một lần nữa trên IE8 trở về trước, tài sản được tạo ra không thể đếm được (không hiển thị trong các for..inbáo cáo). Điều đó thật kỳ quặc, đặc biệt được đưa ra # 3 dưới đây.


# 3 window.a = 0;

Điều này tạo ra một thuộc tính trên đối tượng toàn cầu một cách rõ ràng, sử dụng windowtoàn cục đề cập đến đối tượng toàn cầu (trên trình duyệt; một số môi trường không có trình duyệt có biến toàn cục tương đương, chẳng hạn như globaltrên NodeJS). Vì nó là một tài sản bình thường, bạn có thể xóa nó.

Thuộc tính này là vô số , trên IE8 trở về trước và trên mọi trình duyệt khác mà tôi đã thử.


#4 this.a = 0;

Chính xác như # 3, ngoại trừ chúng tôi đang tham khảo đối tượng toàn cầu thông qua thisthay vì toàn cầu window. Tuy nhiên, điều này sẽ không hoạt động ở chế độ nghiêm ngặt, bởi vì trong mã toàn cầu ở chế độ nghiêm ngặt, thiskhông có tham chiếu đến đối tượng toàn cầu ( undefinedthay vào đó nó có giá trị ).


Xóa thuộc tính

Ý tôi là gì khi "xóa" hoặc "xóa" a? Chính xác là: Xóa thuộc tính (hoàn toàn) thông qua deletetừ khóa:

window.a = 0;
display("'a' in window? " + ('a' in window)); // displays "true"
delete window.a;
display("'a' in window? " + ('a' in window)); // displays "false"

deleteloại bỏ hoàn toàn một tài sản từ một đối tượng. Bạn không thể làm điều đó với các thuộc tính được thêm vào windowgián tiếp thông qua var, deletesẽ âm thầm bị bỏ qua hoặc ném một ngoại lệ (tùy thuộc vào việc triển khai JavaScript và liệu bạn có ở chế độ nghiêm ngặt không).

Cảnh báo : IE8 một lần nữa (và có lẽ sớm hơn và IE9-IE11 ở chế độ "tương thích" bị hỏng): Nó sẽ không cho phép bạn xóa các thuộc tính của windowđối tượng, ngay cả khi bạn được phép. Tồi tệ hơn, nó ném một ngoại lệ khi bạn thử ( thử trải nghiệm này trong IE8 và trong các trình duyệt khác). Vì vậy, khi xóa khỏi windowđối tượng, bạn phải phòng thủ:

try {
    delete window.prop;
}
catch (e) {
    window.prop = undefined;
}

Điều đó cố gắng xóa tài sản, và nếu một ngoại lệ được ném ra, nó sẽ làm điều tốt nhất tiếp theo và đặt thuộc tính undefined.

Điều này chỉ áp dụng cho windowđối tượng và chỉ (theo như tôi biết) đối với IE8 trở về trước (hoặc IE9-IE11 ở chế độ "tương thích" bị hỏng). Các trình duyệt khác đều ổn với việc xóa các windowthuộc tính, tuân theo các quy tắc ở trên.


Khi nào varxảy ra

Các biến được xác định thông qua vartuyên bố được tạo ra trước khi bất kỳ đang từng bước trong bối cảnh thực hiện được điều hành, và do đó tài sản tồn tại tốt trước các vartuyên bố.

Điều này có thể gây nhầm lẫn, vì vậy hãy xem:

display("foo in window? " + ('foo' in window)); // displays "true"
display("window.foo = " + window.foo);          // displays "undefined"
display("bar in window? " + ('bar' in window)); // displays "false"
display("window.bar = " + window.bar);          // displays "undefined"
var foo = "f";
bar = "b";
display("foo in window? " + ('foo' in window)); // displays "true"
display("window.foo = " + window.foo);          // displays "f"
display("bar in window? " + ('bar' in window)); // displays "true"
display("window.bar = " + window.bar);          // displays "b"

Ví dụ trực tiếp:

Như bạn có thể thấy, biểu tượng foođược xác định trước dòng đầu tiên, nhưng biểu tượng barthì không. Trường hợp var foo = "f";câu lệnh là, thực sự có hai điều: xác định ký hiệu, xảy ra trước khi dòng mã đầu tiên được chạy; và thực hiện một nhiệm vụ cho biểu tượng đó, xảy ra khi dòng nằm trong luồng từng bước. Điều này được gọi là " varcẩu" vì var foophần được di chuyển ("Palăng") lên trên cùng của phạm vi, nhưng foo = "f"phần được để lại ở vị trí ban đầu. (Xem Nghèo bị hiểu lầmvar trên blog nhỏ thiếu máu của tôi.)


Khi nào letconstxảy ra

letconstkhác với varmột vài cách Cách liên quan đến câu hỏi là mặc dù ràng buộc mà họ xác định được tạo ra trước khi bất kỳ mã từng bước nào chạy, nhưng nó không thể truy cập được cho đến khi đạt được câu lệnh lethoặc constcâu lệnh.

Vì vậy, trong khi điều này chạy:

display(a);    // undefined
var a = 0;
display(a);    // 0

Điều này đưa ra một lỗi:

display(a);    // ReferenceError: a is not defined
let a = 0;
display(a);

Hai cách khác letconstkhác với var, không thực sự phù hợp với câu hỏi, là:

  1. varluôn luôn áp dụng cho toàn bộ bối cảnh thực hiện (trong cả mã toàn cầu, hoặc trong cả mã chức năng trong hàm mà nó xuất hiện), nhưng letconstchỉ áp dụng trong phạm vi khối nơi họ xuất hiện. Đó là, varcó chức năng (hoặc toàn cầu) phạm vi, nhưng letconstcó phạm vi khối.

  2. Lặp lại var atrong cùng một bối cảnh là vô hại, nhưng nếu bạn có let a(hoặc const a), có một let ahoặc một const ahoặc một var alỗi là một cú pháp.

Dưới đây là một ví dụ chứng minh điều đó letconstcó hiệu lực ngay lập tức trong khối của họ trước khi bất kỳ mã nào trong khối đó chạy, nhưng không thể truy cập được cho đến khi câu lệnh lethoặc const:

var a = 0;
console.log(a);
if (true)
{
  console.log(a); // ReferenceError: a is not defined
  let a = 1;
  console.log(a);
}

Lưu ý rằng lần thứ hai console.logkhông thành công, thay vì truy cập atừ bên ngoài khối.


Off-topic: Tránh làm lộn xộn đối tượng toàn cầu ( window)

Đối windowtượng được rất, rất lộn xộn với các thuộc tính. Bất cứ khi nào có thể, mạnh mẽ đề nghị không thêm vào mớ hỗn độn. Thay vào đó, hãy gói các biểu tượng của bạn trong một gói nhỏ và xuất tối đa một biểu tượng cho windowđối tượng. (Tôi thường không xuất bất kỳ ký hiệu nào cho windowđối tượng.) Bạn có thể sử dụng một hàm để chứa tất cả mã của mình để chứa các ký hiệu của bạn và hàm đó có thể ẩn danh nếu bạn muốn:

(function() {
    var a = 0; // `a` is NOT a property of `window` now

    function foo() {
        alert(a);   // Alerts "0", because `foo` can access `a`
    }
})();

Trong ví dụ đó, chúng tôi xác định một hàm và thực hiện nó ngay lập tức ( ()ở cuối).

Một hàm được sử dụng theo cách này thường được gọi là hàm phạm vi . Các hàm được xác định trong hàm phạm vi có thể truy cập các biến được xác định trong hàm phạm vi vì chúng đóng trên dữ liệu đó (xem: Đóng không phức tạp trên blog nhỏ thiếu máu của tôi).


Tôi có thể làm gì window['a']=0để làm rõ rằng tôi đang sử dụng cửa sổ làm bản đồ không? có gì windowđặc biệt khi một số trình duyệt không cho phép điều này và buộc tôi sử dụng window.a?
Jayen 27/2/2015

Một lưu ý về số 3 có lẽ đáng để làm rõ: window.a = 0;chỉ hoạt động trong môi trường trình duyệt và chỉ theo quy ước. Liên kết đối tượng toàn cầu với một biến có tên windowkhông có trong ES Spec và do đó sẽ không hoạt động, ví dụ, V8 hoặc Node.js, trong khi this.a = 0;(khi được gọi trong bối cảnh thực thi toàn cầu) sẽ hoạt động trong mọi môi trường do thông số kỹ thuật đó chỉ định rằng phải có một đối tượng toàn cầu. Nếu gói mã của bạn trong IIFE như trong phần Off-topic , bạn có thể chuyển thisdưới dạng tham số có tên windowhoặc globalđể có được tham chiếu trực tiếp đến đối tượng toàn cục.
Sherlock_HJ

@Sherlock_HJ: Tôi đã thêm "trên trình duyệt;" đó là sớm hơn trong câu trả lời là tốt, nhưng tôi đã thêm nó trong trường hợp mọi người bỏ qua điều đó. Đó là trong thông số kỹ thuật bây giờ ; trong khi nó chỉ đi qua, bạn sẽ không tìm thấy một trình duyệt không làm điều đó. Tôi là một chút ngạc nhiên khi nó không có trong Phụ lục B .
TJ Crowder

@TJCrowder, Vì vậy, một biến toàn cục được khai báo var a = 0;tự động trở thành một thuộc tính của đối tượng toàn cầu. Nếu tôi khai báo var b = 0;trong một khai báo hàm, nó cũng sẽ là một thuộc tính của một số đối tượng cơ bản?
ezpresso

@ezpresso: Không và có. Họ trở thành thuộc tính của một đối tượng (các EnvironmentRecord của VariableEnvironment của ExecutionContext nơi họ xuất hiện; chi tiết ở đâyở đây ), nhưng không có cách nào để truy cập trực tiếp mà đối tượng từ mã chương trình.
TJ Crowder

40

Giữ cho nó đơn giản:

a = 0

Đoạn mã trên cung cấp một biến phạm vi toàn cầu

var a = 0;

Mã này sẽ đưa ra một biến được sử dụng trong phạm vi hiện tại và theo nó

window.a = 0;

Điều này thường giống như biến toàn cục.


Câu lệnh của bạn "Đoạn mã trên đưa ra biến phạm vi toàn cầu" "Mã này sẽ cung cấp cho một biến được sử dụng trong phạm vi hiện tại, và dưới nó" , lấy nhau, gợi ý rằng bạn không thể sử dụng dòng đầu tiên và tiếp cận a dưới các phạm vi hiện tại. Bạn có thể. Ngoài ra, việc bạn sử dụng "biến toàn cục" là một chút - hai nơi bạn nói "biến toàn cầu" không toàn cầu hơn nơi bạn không nói.
TJ Crowder

bản thân toàn cầu có nghĩa là bạn có thể truy cập / đọc / ghi biến ở bất cứ đâu, kể cả nơi tôi đã đề cập đến phạm vi hiện tại, điều đó quá rõ ràng. Và nếu bạn đề xuất window.a và 'a' sẽ không có tính toàn cầu trong tập lệnh thì bạn đã sai 100%.
Umair Jabbar

3
@Umair: "bản thân toàn cầu có nghĩa là bạn có thể truy cập / đọc / ghi biến ở bất cứ đâu" Đúng. Một lần nữa, bạn dường như đang gọi đầu tiên và cuối cùng là "toàn cầu" hơn là giữa, tất nhiên họ không.
TJ Crowder

4
cái ở giữa được coi là được sử dụng bên trong một hàm, tất cả chúng sẽ giống nhau nếu được sử dụng trong phạm vi chính. sử dụng var bên trong một chức năng là giả định của tôi
Umair Jabbar

4
@Umair: "sử dụng var bên trong một chức năng là giả định của tôi" Ah, okay. Nhưng đó không phải là câu hỏi. Câu hỏi nói rất rõ ràng "trong phạm vi toàn cầu" . Nếu bạn sẽ thay đổi giả định (đủ công bằng, để mở rộng và giải thích một điểm chung hơn), bạn sẽ cần phải rõ ràng đó là những gì bạn đang làm trong câu trả lời của mình.
TJ Crowder

10
<title>Index.html</title>
<script>
    var varDeclaration = true;
    noVarDeclaration = true;
    window.hungOnWindow = true;
    document.hungOnDocument = true;
</script>
<script src="external.js"></script>

/* external.js */

console.info(varDeclaration == true); // could be .log, alert etc
// returns false in IE8

console.info(noVarDeclaration == true); // could be .log, alert etc
// returns false in IE8

console.info(window.hungOnWindow == true); // could be .log, alert etc
// returns true in IE8

console.info(document.hungOnDocument == true); // could be .log, alert etc
// returns ??? in IE8 (untested!)  *I personally find this more clugy than hanging off window obj

Có một đối tượng toàn cầu mà tất cả các vars được treo theo mặc định? ví dụ: 'globalals.noVar tuyên bố'


Khám phá rất tốt đẹp. Hướng dẫn xác định để sử dụng window.*khai báo. Tuyên bố này có vẻ an toàn nhất đối với việc sao chép mã của bạn và cũng rõ ràng.
Dan

7

Bassed trên câu trả lời tuyệt vời của TJ Crowder : ( Off-topic: Tránh lộn xộnwindow )

Đây là một ví dụ về ý tưởng của anh ấy:

Html

<!DOCTYPE html>
<html>
  <head>
    <script type="text/javascript" src="init.js"></script>
    <script type="text/javascript">
      MYLIBRARY.init(["firstValue", 2, "thirdValue"]);
    </script>
    <script src="script.js"></script>
  </head>

  <body>
    <h1>Hello !</h1>
  </body>    
</html>

init.js (Dựa trên câu trả lời này )

var MYLIBRARY = MYLIBRARY || (function(){
    var _args = {}; // private

    return {
        init : function(Args) {
            _args = Args;
            // some other initialising
        },
        helloWorld : function(i) {
            return _args[i];
        }
    };
}());

script.js

// Here you can use the values defined in the html as if it were a global variable
var a = "Hello World " + MYLIBRARY.helloWorld(2);

alert(a);

Đây là plnkr . Hy vọng nó sẽ giúp!


5

Trong phạm vi toàn cầu không có sự khác biệt về ngữ nghĩa.

Nhưng bạn thực sự nên tránh a=0vì bạn đặt giá trị cho một biến không được khai báo.

Đồng thời sử dụng các bao đóng để tránh chỉnh sửa phạm vi toàn cầu

(function() {
   // do stuff locally

   // Hoist something to global scope
   window.someGlobal = someLocal
}());

Luôn luôn sử dụng các bao đóng và luôn luôn nâng lên phạm vi toàn cầu khi nó hoàn toàn cần thiết. Bạn nên sử dụng xử lý sự kiện không đồng bộ cho hầu hết các giao tiếp của bạn.

Như @AvianMoncellor đã đề cập, có một lỗi IE với var a = foo chỉ khai báo toàn cầu cho phạm vi tệp. Đây là một vấn đề với trình thông dịch bị hỏng khét tiếng của IE. Lỗi này nghe có vẻ quen thuộc nên có lẽ đúng.

Vì vậy, dính vào window.globalName = someLocalpointer


2
"Trong phạm vi toàn cầu không có sự khác biệt về ngữ nghĩa." Trên thực tế, có một sự khác biệt lớn về ngữ nghĩa, các cơ chế mà tài sản được xác định là hoàn toàn khác nhau - nhưng về mặt thực tế, nó chỉ có một sự khác biệt thực tế nhỏ (trong đó bạn không thể deletea var).
TJ Crowder

@TJ Crowder Tôi không biết điều đó. Tôi nghĩ khai báo biến là thiết lập các thuộc tính trên đối tượng biến. Không biết những cái đó không thể bị xóa.
Raynos

Vâng Chúng cũng được xác định sớm hơn nếu bạn sử dụng var. Chúng chỉ là những cơ chế hoàn toàn khác nhau có cùng kết quả thực tế. :-)
TJ Crowder

@TJ Crowder Tôi quên đề cập rằng varnhảy đến điểm dừng của phạm vi.
Raynos
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.