Các biến JavaScript khai báo bên ngoài hoặc bên trong vòng lặp?


212

Trong AS3 tôi tin rằng bạn nên khởi tạo tất cả các biến ngoài vòng lặp để tăng hiệu suất. Đây có phải là trường hợp với JavaScript không? Cái nào tốt hơn / nhanh hơn / thực hành tốt nhất?

var value = 0;

for (var i = 0; i < 100; i++)
{
    value = somearray[i];
}

hoặc là

for (var i = 0 ; i < 100; i++)
{
    var value = somearray[i];
}

7
Ở ngoài! luôn ở bên ngoài
BGerrissen

37
Hmm, không khai báo biến được đẩy lên phạm vi chức năng nào trong cả Javascript và AS3? Nếu tôi đúng, thì nó thực sự không thành vấn đề.
tiêu

3
@Andy - bạn đã thử gán trước khi khai báo trong một thân hàm chưa? Có lẽ định kiến ​​của bạn đang dẫn bạn lạc lối. Hiệu suất WRT, với phạm vi đẩy lên, nếu JS được giải thích, thì nó sẽ nhai thêm các chu kỳ trong một khối vòng lặp. Nếu được biên dịch (mà hầu hết các động cơ làm ngày nay) nó sẽ không thành vấn đề.
tiêu

2
Câu hỏi tuyệt vời! Cảm ơn. Sau khi đọc tất cả các câu trả lời, tôi tin rằng nếu đó chỉ là một vòng lặp nhỏ hoặc chỉ là một biến tạm thời, tôi sẽ giữ chúng ở nơi cần thiết và nó không ảnh hưởng đến hiệu suất. Nếu một var được sử dụng trong một hàm nhiều hơn một lần, tại sao không tham chiếu đến nó bên trong hàm và cuối cùng là toàn cầu thì có thể được đặt bên ngoài fn ()
Dean Meehan

3
Tôi ngạc nhiên không ai cố gắng đo lường hiệu suất. Tôi đã tạo ra một jsperf . Có vẻ nhanh hơn một chút khi được khai báo bên trong vòng lặp cho Safari và Firefox, ngược lại với Chrome,
Buzut

Câu trả lời:


281

hoàn toàn không có sự khác biệt về ý nghĩa hoặc hiệu suất, trong JavaScript hoặc ActionScript.

varlà một lệnh cho trình phân tích cú pháp và không phải là lệnh được thực thi trong thời gian chạy. Nếu một mã định danh cụ thể đã được khai báo varmột lần hoặc nhiều hơn bất cứ nơi nào trong thân hàm (*), thì tất cả việc sử dụng mã định danh đó trong khối sẽ được tham chiếu đến biến cục bộ. Không có sự khác biệt cho dù valueđược tuyên bố là varbên trong vòng lặp, bên ngoài vòng lặp hoặc cả hai.

Do đó, bạn nên viết bất cứ điều gì bạn thấy dễ đọc nhất. Tôi không đồng ý với Crockford rằng đặt tất cả các vars lên hàng đầu của một chức năng luôn là điều tốt nhất. Đối với trường hợp một biến được sử dụng tạm thời trong một phần của mã, tốt hơn là khai báo vartrong phần đó, vì vậy phần đó đứng một mình và có thể được sao chép. Mặt khác, sao chép-dán một vài dòng mã vào một chức năng mới trong quá trình tái cấu trúc, mà không chọn riêng và di chuyển liên kết var, và bạn đã có cho mình một thế giới tình cờ.

Đặc biệt:

for (var i; i<100; i++)
    do something;

for (var i; i<100; i++)
    do something else;

Crockford sẽ khuyên bạn nên loại bỏ cái thứ hai var(hoặc loại bỏ cả vars và làm var i;ở trên), và jslint sẽ rên rỉ với bạn vì điều này. Nhưng IMO sẽ dễ duy trì hơn để giữ cả hai vars, giữ tất cả các mã liên quan với nhau, thay vì có một đoạn mã bổ sung, dễ bị lãng quên ở đầu hàm.

Cá nhân tôi có xu hướng khai báo là varlần gán đầu tiên của một biến trong một phần mã độc lập, cho dù có sử dụng riêng một tên biến khác trong một phần khác của cùng một hàm hay không. Đối với tôi, việc phải khai báo vartất cả là một mụn cóc JS không mong muốn (tốt hơn là nên có các biến mặc định thành cục bộ); Tôi không coi nhiệm vụ của mình là sao chép các giới hạn của [bản sửa đổi cũ của] ANSI C trong JavaScript.

(*: khác với các hàm chức năng lồng nhau)


4
Tôi vẫn không thể quyết định liệu tôi có ở với Crockford hay không. Khai báo các biến trong các khối có cảm giác hơi giống như sử dụng các câu lệnh hàm có điều kiện (rất nghịch ngợm) ... Tuy nhiên, tôi cũng đồng ý với quan điểm của bạn :) # bối rối
Daniel Vassallo

20
+1 Tôi không đồng ý với lý luận của Crockford (được trích dẫn trong câu trả lời của Daniel), vì các nhà phát triển JavaScript chúng ta không nên viết mã để các lập trình viên "gia đình C" khác có thể hiểu được. Tôi thường sử dụng var bên trong nếu khối và vòng lặp vì nó có ý nghĩa hơn đối với tôi.
Andy E

4
-1 OP đang hỏi liệu các vars trong phần thân của vòng lặp có nên được khai báo trước khi vòng lặp bắt đầu không. Giá trị chỉ số của vòng lặp rõ ràng là một trường hợp đặc biệt (và được nâng lên) và hoàn toàn không giúp ích gì cho OP.
mkoistinen

21
Các chỉ mục vòng lặp không phải là trường hợp đặc biệt, chúng được xử lý và nâng lên chính xác giống như cách gán thông thường.
bobince

31
+1 Crockford đã sai về điều này (và những người khác, nhưng tôi lạc đề). Yêu cầu varchỉ được sử dụng ở đầu hàm chỉ là yêu cầu tạo biến toàn cục ngẫu nhiên. Và việc có một khối lượng các biến không liên quan, tất cả được khai báo tại một điểm là vô nghĩa về mặt ngữ nghĩa, đặc biệt khi một số biến đó có thể không bao giờ được sử dụng.
MooGoo

64

Về lý thuyết, nó không nên tạo ra bất kỳ sự khác biệt nào trong JavaScript, vì ngôn ngữ không có phạm vi chặn, mà chỉ có phạm vi chức năng.

Tôi không chắc chắn về đối số hiệu suất, nhưng Douglas Crockford vẫn khuyến nghị rằng các varcâu lệnh nên là câu lệnh đầu tiên trong thân hàm. Trích dẫn từ các quy ước mã cho ngôn ngữ lập trình JavaScript :

JavaScript không có phạm vi khối, vì vậy việc xác định các biến trong các khối có thể gây nhầm lẫn cho các lập trình viên có kinh nghiệm với các ngôn ngữ gia đình C khác. Xác định tất cả các biến ở đầu hàm.

Tôi nghĩ rằng anh ta có một điểm, như bạn có thể thấy trong ví dụ sau. Khai báo các biến ở đầu hàm không được khiến người đọc nhầm lẫn rằng biến đó i được giữ trong phạm vi của forkhối vòng lặp:

function myFunction() {
  var i;    // the scope of the variables is very clear

  for (i = 0; i < 10; i++) {
    // ...
  }
}

8
+1 để nói sự thật với OP về phạm vi JS. Tôi đang tự hỏi liệu có nên downvote câu trả lời mà nói khác!
nước chi tiêu

1
@Kieranmaine: Tôi không nói nó không ảnh hưởng đến hiệu suất. Tôi vừa đưa ra một lập luận cho việc đặt chúng bên ngoài các vòng lặp, không liên quan đến hiệu suất .... Tôi không có bất kỳ tài liệu tham khảo nào cho các đối số hiệu suất, nhưng bạn cũng không trích dẫn bất kỳ câu trả lời nào của bạn :)
Daniel Vassallo

1
@Kieranmaine: bạn có nguồn nào cho việc đó không?
Andy E

5
@Kieranmaine: AFAIK ngay cả khi bạn khai báo các biến trong vòng lặp, ecma- / javascriptsẽ làm hỏng chúng khi chạy. Điều đó được gọi là "Tời". Vì vậy, không nên có bất kỳ sự khác biệt.
jAndy

1
ES6 letảnh hưởng đến câu trả lời này như thế nào?
jbyrd

58

Các ECMA-/Javascriptngôn ngữ hoistsbất kỳ biến mà được khai báo bất cứ nơi nào để phía trên cùng của một hàm. Đó là bởi vì ngôn ngữ nàyfunction scopekhôngblock scopenhiều ngôn ngữ giống như C khác.
Điều đó còn được gọi là lexical scope.

Nếu bạn tuyên bố một cái gì đó như

var foo = function(){
    for(var i = 0; i < 10; i++){
    }
};

Điều này được hoisted:

var foo = function(){
    var i;
    for(i = 0; i < 10; i++){
    }
}

Vì vậy, nó không tạo ra bất kỳ sự khác biệt nào trong hiệu suất (Nhưng hãy sửa tôi nếu tôi hoàn toàn sai ở đây).
Một đối số tốt hơn nhiều cho việc không khai báo một biến ở một nơi nào khác ngoài đầu hàm là khả năng đọc . Khai báo một biến trong một for-loopcó thể dẫn đến giả định sai rằng biến này chỉ có thể được truy cập trong thân vòng lặp, điều này hoàn toàn sai . Nguyên vẹn bạn có thể truy cập vào biến đó bất cứ nơi nào trong phạm vi hiện tại.


Câu trả lời cơ bản giống như câu trả lời được chấp nhận nhưng, IMO, dễ đọc hơn và gần như là thông tin. Công việc tốt.
Anne Gunn

5
ES6 letảnh hưởng đến câu trả lời này như thế nào?
jbyrd

13

Năm tới, tất cả các trình duyệt sẽ có các công cụ JS biên dịch mã trước, do đó, sự khác biệt về hiệu năng (xuất phát từ việc phân tích cùng một khối mã nhiều lần cộng với việc thực hiện gán) sẽ trở nên không đáng kể.

Ngoài ra, không bao giờ tối ưu hóa cho hiệu suất trừ khi bạn phải. Giữ các biến gần với nơi bạn cần đến lần đầu tiên để giữ mã của bạn sạch sẽ. Về mặt tiêu cực, những người đã quen với các ngôn ngữ có phạm vi khối có thể bị nhầm lẫn.


6

Một xem xét khác, bây giờ chúng ta có letconsttrong ES2015, là bây giờ bạn có thể phạm vi các biến cụ thể cho khối vòng lặp. Vì vậy, trừ khi bạn sẽ cần cùng một biến bên ngoài vòng lặp (hoặc nếu mỗi lần lặp phụ thuộc vào một thao tác được thực hiện cho biến đó trong lần lặp trước đó), có lẽ nên làm điều này:

for (let i = 0; i < 100; i++) {
    let value = somearray[i];
    //do something with `value`
}

4

Tôi vừa làm một thử nghiệm đơn giản trong Chrome. Hãy thử fiddle trong trình duyệt của bạn và xem kết quả

  var count = 100000000;
    var a = 0;
    console.log(new Date());

    for (var i=0; i<count; i++) {
      a = a + 1
    }

    console.log(new Date());

    var j;
    for (j=0; j<count; j++) {
      a = a + 1;
    }

    console.log(new Date());

    var j;
    for (j=0; j<count; j++) {
        var x;
        x = x + 1;
    }

    console.log(new Date());

Kết quả là bài kiểm tra cuối cùng mất ~ 8 giây và 2 lần trước chỉ ~ 2 giây. Rất lặp lại và bất kể thứ tự.

Vì vậy, điều này chứng tỏ với tôi, rằng người ta phải luôn luôn khai báo các vars bên ngoài vòng lặp. Trường hợp tò mò với tôi là trường hợp đầu tiên tôi khai báo itrong câu lệnh for (). Bài kiểm tra này dường như nhanh như bài kiểm tra thứ 2 nơi tôi khai báo trước chỉ mục.


14
@KP: kết quả chỉ được chứng minh nếu bạn tự kiểm tra chúng hoặc nếu một số lượng lớn người xác minh chúng. @mkoistinen: Tôi đã xây dựng một bài kiểm tra công bằng hơn, jsfiddle.net/GM8nk . Sau khi chạy tập lệnh một vài lần trong Chrome 5, tôi có thể thấy rằng không có người chiến thắng nhất quán. Tất cả ba biến thể thực hiện tốt hơn so với các biến thể khác sau một vài lần làm mới. -1 từ tôi, tôi sợ. Lưu ý, bạn có thể muốn chạy cái này trong các trình duyệt khác. IE và Fx không giống như 100 triệu lần lặp.
Andy E

1
@AndyE. Wow, vậy dựa trên thử nghiệm đơn giản này, IE hút hơn 100 lần? =)
mkoistinen

2
Kết quả ở khắp mọi nơi đối với tôi, không có người chiến thắng trên nhiều trình duyệt rõ ràng mặc dù đôi khi có sự khác biệt đáng kể về tốc độ. Kỳ dị. Tôi nghĩ rằng Fiddle của Andy là một thử nghiệm tốt hơn, mặc dù, đặt mỗi ứng cử viên vào chức năng riêng của mình ... chắc chắn nếu tập lệnh gốc được chạy bên ngoài chức năng, thì thực sự không nên thử nghiệm bất cứ điều gì khi vartuyên bố là biến toàn cục. dù sao cũng là toàn cầu.
bobince

4
Hơn một năm sau thực tế, nhưng SHAPOW
sdleihssirhc

2
Đây không phải là của tôi, nhưng tôi cho rằng một số bạn sẽ quan tâm: jsperf.com/var-in-for-loop
m1.

1

JavaScript là ngôn ngữ được viết ở phía dưới bởi C hoặc C ++, tôi không chắc đó là ngôn ngữ nào. Và một trong những mục đích của nó là tiết kiệm công sức xử lý bộ nhớ trong. Ngay cả trong C hoặc C ++, bạn sẽ không phải lo lắng về việc liệu nó sẽ tiêu tốn nhiều tài nguyên khi các biến được khai báo trong một vòng lặp. Tại sao bạn nên lo lắng về nó trong JavaScript?


1
C hoặc C ++ có thể nằm ở cuối JavaScript. Nhưng chúng ta không nên quên rằng, trình duyệt chuyển đổi JavaScript sang ngôn ngữ cơ bản (C, C ++). Vì vậy, hiệu suất phụ thuộc vào trình duyệt
Kira

3
@Kira Nó không thực sự được chuyển đổi thành C / C ++. Javascript được biên dịch thành một tập hợp các hướng dẫn được thực thi bởi bộ thực thi JS (tức là một máy ảo đang chạy trong trình duyệt). Nguyên tắc tương tự áp dụng cho các ngôn ngữ động khác như Python và Ruby.
Anthony E

@AnthonyE, Cảm ơn thông tin. Tôi không chắc chắn về việc chuyển đổi JS sang C hoặc C ++. Vì vậy, tôi đã sử dụng có thể trong nhận xét của mình
Kira

0

Chà, điều đó phụ thuộc vào những gì bạn đang cố gắng đạt được ... nếu valuegiả sử chỉ là một biến tạm thời bên trong khối vòng lặp thì việc sử dụng hình thức thứ hai sẽ rõ ràng hơn nhiều. Nó cũng hợp lý và dài dòng hơn.


Tôi đã thấy rằng việc đẩy tất cả các khai báo biến lên đầu - bao gồm các biến tạm thời - thực sự có thể dẫn đến sự nhầm lẫn vì nó chỉ là 'ồn ào'.
Daniel Sokolowski

0

Nó không tạo ra sự khác biệt nếu bạn khai báo các biến bên trong hoặc bên ngoài vòng lặp for. Dưới đây là mã mẫu để kiểm tra.

function a() {
   console.log('Function a() starts');
   console.log(new Date());
    var j;
    for (j=0; j<100000000; j++) {
        var x;
        x = x + 1;
    }
    console.log(new Date());
    console.log('Function a() Ends');
}
a()
function b() {
console.log('Function B() starts');
   console.log(new Date());
    var a;
    var j;
    for (j=0; j<100000000; j++) {
      a = a + 1;
    }
    console.log(new Date());
    console.log('Function B() Ends');
}
b()

Kết quả cho thấy trong trường hợp của tôi

Function a() starts
VM121:3 Thu Apr 12 2018 15:20:26 GMT+0530 (India Standard Time)
VM121:9 Thu Apr 12 2018 15:20:26 GMT+0530 (India Standard Time)
VM121:10 Function a() Ends
VM121:14 Function B() starts
VM121:15 Thu Apr 12 2018 15:20:26 GMT+0530 (India Standard Time)
VM121:21 Thu Apr 12 2018 15:20:26 GMT+0530 (India Standard Time)
VM121:22 Function B() Ends

Cảm ơn bạn - MyFavs.in


trong cả hai trường hợp bạn khai báo j ngoài vòng lặp! X_x
john ktejik

Tôi đã thử nó trong Chromium 81 letthay vì vara()có xu hướng chậm hơn một chút (như 120 so với 115 ms = ~ 6% = IMO không đáng kể)
mikiqex

-1

Câu hỏi ở đây về cơ bản là khai báo một var bên trong một vòng lặp. Chỉ cần nghĩ những gì sẽ xảy ra nếu bạn làm điều này:

var a = 30;
var a = 50;
var a = 60;

Bạn có nghĩ điều này đúng không? Không ... bởi vì bạn không muốn khai báo một biến quá nhiều lần. Khi bạn khai báo một biến trong một vòng lặp, nó không khai báo bao nhiêu lần vòng lặp chạy? Rõ ràng là nó sẽ tát bạn khi bạn ở chế độ 'sử dụng nghiêm ngặt'. Mọi người đã không đồng ý với Crockford mà không nghĩ về câu hỏi ban đầu.

Vì vậy, luôn luôn tốt khi khai báo các biến trên đầu - 1. Để dễ đọc, 2. Tạo thói quen tốt.


1
"Khi bạn khai báo một biến trong vòng lặp, nó không khai báo bao nhiêu lần vòng lặp chạy?" <- Không, điều đó không đúng. Khai báo biến được nâng lên, vì vậy tất cả những gì bạn còn lại là gán.
Matthias

-2

Liên quan đến hiệu suất sau khi chạy thử nghiệm trên Chrome, Firefox và jsperf trên HĐH Linux, dường như có sự khác biệt về hiệu suất giữa việc khai báo các biến trong một vòng lặp và ngoài vòng lặp. Đó là một sự khác biệt nhỏ nhưng điều này cũng được kết hợp bởi số lần lặp và số lượng khai báo biến.

Do đó để có hiệu suất tốt nhất tôi sẽ phải đề xuất khai báo các biến ngoài vòng lặp. Hoặc tốt hơn là khai báo các biến của bạn trong dòng. Xem ví dụ.

// inline
for (var ai = 0, al = 100000000, av; ai < al; ai++) {
    av = av + 1;
}

// outside
var bv;
var bl = 100000000;
for (var bi = 0; bi < bl; bi++) {
    bv = bv + 1;
}

Lưu ý cách biến 'al' và 'av' nằm trong dòng khai báo vòng lặp for. Tuyên bố nội tuyến này đã cung cấp cho tôi hiệu suất tốt hơn liên tục. Thậm chí qua việc khai báo các biến ngoài vòng lặp. Một lần nữa sự khác biệt hiệu suất là rất nhỏ.

https://jsperf.com/outside-inline-for-loop-ase/1


Đối với tôi bài kiểm tra của bạn đã cho bên trong vòng lặp. Và tuy nhiên, không có gì khác biệt, quá nhỏ để kết luận và câu trả lời được chấp nhận giải thích rõ ràng không có sự khác biệt
Ulysse BN

Khi khai báo biến được nâng lên, thực sự không có sự khác biệt.
trincot
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.