2008-09-17 42 views
72

Tôi đã tự hỏi, liệu có sự khác biệt về hiệu suất giữa việc sử dụng các hàm được đặt tên và các hàm ẩn danh trong Javascript không?Việc sử dụng các chức năng ẩn danh có ảnh hưởng đến hiệu suất không?

for (var i = 0; i < 1000; ++i) { 
    myObjects[i].onMyEvent = function() { 
     // do something 
    }; 
} 

vs

function myEventHandler() { 
    // do something 
} 

for (var i = 0; i < 1000; ++i) { 
    myObjects[i].onMyEvent = myEventHandler; 
} 

Đầu tiên là ngăn nắp vì nó không lộn xộn lên mã của bạn với chức năng hiếm khi được sử dụng, nhưng không nó vấn đề mà bạn đang tái tuyên bố rằng hàm này nhiều lần?

+0

Tôi biết nó không có trong câu hỏi, nhưng liên quan đến code-cleanliness/legibility Tôi nghĩ rằng 'đúng cách' là một nơi nào đó ở giữa. "Sự lộn xộn" của các hàm mức cao nhất hiếm khi được sử dụng là gây phiền nhiễu, nhưng vì vậy mã lồng nhau phụ thuộc rất nhiều vào các hàm ẩn danh được khai báo trong dòng với lời gọi của chúng (xem địa chỉ gọi lại node.js). Cả hai trước đây và sau này có thể thực hiện gỡ lỗi/thực hiện truy tìm khó khăn. –

+0

Các thử nghiệm hiệu suất dưới đây chạy hàm cho hàng nghìn lần lặp. Ngay cả khi bạn thấy sự khác biệt đáng kể, phần lớn các trường hợp sử dụng sẽ không làm điều này trong các lần lặp lại của thứ tự đó. Do đó tốt hơn nên chọn bất cứ điều gì phù hợp với nhu cầu của bạn và bỏ qua hiệu suất cho trường hợp cụ thể này. – Medorator

+0

@nickf tất nhiên là câu hỏi quá cũ của nó, nhưng hãy xem câu trả lời cập nhật mới –

Trả lời

74

Vấn đề hiệu suất ở đây là chi phí của việc tạo ra một đối tượng hàm mới tại mỗi lần lặp của vòng lặp và không phải là thực tế rằng bạn sử dụng một ẩn danh chức năng:

for (var i = 0; i < 1000; ++i) {  
    myObjects[i].onMyEvent = function() { 
     // do something  
    }; 
} 

Bạn đang tạo một nghìn đối tượng chức năng riêng biệt mặc dù chúng có cùng một phần mã và không ràng buộc với phạm vi từ vựng ()). Sau đây dường như nhanh hơn, mặt khác, bởi vì nó chỉ đơn giản là gán các cùng tham khảo chức năng để các phần tử mảng trong suốt vòng lặp:

function myEventHandler() { 
    // do something 
} 

for (var i = 0; i < 1000; ++i) { 
    myObjects[i].onMyEvent = myEventHandler; 
} 

Nếu bạn đã tạo ra các chức năng ẩn danh trước khi vào vòng lặp, sau đó chỉ gán tham chiếu đến nó vào phần tử mảng trong khi bên trong vòng lặp, bạn sẽ thấy rằng không có hiệu suất hoặc ngữ nghĩa khác biệt nào khi so sánh với phiên bản hàm có tên:

var handler = function() { 
    // do something  
}; 
for (var i = 0; i < 1000; ++i) {  
    myObjects[i].onMyEvent = handler; 
} 

Nói tóm lại, không có chi phí thực hiện quan sát để sử dụng các hàm ẩn danh có tên.

Là một sang một bên, nó có thể xuất hiện từ trên cao mà không có sự khác biệt giữa:

function myEventHandler() { /* ... */ } 

và:

var myEventHandler = function() { /* ... */ } 

Điều thứ nhất là một tuyên bố chức năng trong khi sau này là một biến gán cho một hàm ẩn danh. Mặc dù chúng có vẻ có cùng tác dụng, nhưng JavaScript lại xử lý chúng một cách hơi khác nhau. Để hiểu sự khác biệt, tôi khuyên bạn nên đọc “JavaScript function declaration ambiguity”.

Thời gian thực thi thực tế cho bất kỳ phương pháp tiếp cận nào phần lớn sẽ được quyết định bởi việc triển khai trình biên dịch và thời gian chạy của trình duyệt. Để so sánh hoàn toàn hiệu suất trình duyệt hiện đại, hãy truy cập vào the JS Perf site

+0

Bạn quên dấu ngoặc đơn trước phần thân hàm. Tôi chỉ thử nghiệm nó, họ được yêu cầu. –

+0

@chinoto Cảm ơn bạn đã chỉ ra sự thiếu sót. Đã sửa! –

+0

có vẻ như kết quả benchmark rất phụ thuộc vào động cơ js! – aleclofabbro

2

Là nguyên tắc thiết kế chung, bạn nên tránh implimenting cùng một mã nhiều lần. Thay vào đó, bạn nên nâng mã phổ biến ra thành một hàm và thực hiện hàm đó (chung, được kiểm tra tốt, dễ sửa đổi) từ nhiều vị trí.

Nếu (không giống như những gì bạn suy ra từ câu hỏi của bạn), bạn đang khai báo hàm bên trong một lần và sử dụng mã đó một lần (và không có gì khác giống trong chương trình của bạn) thì hàm anonomous có thể là (thats a guess folks) được xử lý theo cùng một cách bởi trình biên dịch như một hàm được đặt tên bình thường.

Tính năng rất hữu ích trong các trường hợp cụ thể, nhưng không được sử dụng trong nhiều trường hợp.

1

Tôi không mong đợi nhiều sự khác biệt nhưng nếu có khả năng nó sẽ thay đổi tùy theo công cụ tạo tập lệnh hoặc trình duyệt.

Nếu bạn thấy mã dễ bị lúng túng hơn, hiệu suất sẽ không thành vấn đề trừ khi bạn muốn gọi hàm đó hàng triệu lần.

0

gì chắc chắn sẽ làm cho vòng lặp của bạn nhanh hơn trên một loạt các trình duyệt, đặc biệt là các trình duyệt IE, được lặp như sau:

for (var i = 0, iLength = imgs.length; i < iLength; i++) 
{ 
    // do something 
} 

Bạn đã đặt trong một tùy 1000 vào điều kiện vòng lặp, nhưng bạn sẽ có được của tôi trôi dạt nếu bạn muốn đi qua tất cả các mục trong mảng.

20

Đây là mã thử nghiệm của tôi:

var dummyVar; 
function test1() { 
    for (var i = 0; i < 1000000; ++i) { 
     dummyVar = myFunc; 
    } 
} 

function test2() { 
    for (var i = 0; i < 1000000; ++i) { 
     dummyVar = function() { 
      var x = 0; 
      x++; 
     }; 
    } 
} 

function myFunc() { 
    var x = 0; 
    x++; 
} 

document.onclick = function() { 
    var start = new Date(); 
    test1(); 
    var mid = new Date(); 
    test2(); 
    var end = new Date(); 
    alert ("Test 1: " + (mid - start) + "\n Test 2: " + (end - mid)); 
} 

Kết quả:
Test 1: 142ms thử nghiệm 2: 1983ms

Dường như động cơ JS không nhận ra rằng đó là chức năng tương tự trong Test2 và biên dịch nó mỗi lần.

+3

Trong đó trình duyệt đã được thử nghiệm này được tiến hành? – andnil

+5

Lần cho tôi trên Chrome 23: (2ms/17ms), IE9: (20ms/83ms), FF 17: (2ms/96ms) – Davy8

+0

Câu trả lời của bạn xứng đáng với trọng lượng hơn. Thời gian của tôi trên Intel i5 4570S: Chrome 41 (1/9), IE11 (1/25), FF36 (1/14). Rõ ràng hàm ẩn danh trong vòng lặp thực hiện tồi tệ hơn. – ThisClark

0

@nickf

Đó là một thử nghiệm khá khờ dại, mặc dù bạn đang so sánh việc thực hiện và biên soạn thời gian đó mà rõ ràng là sẽ phương pháp 1 chi phí (biên dịch N lần, động cơ JS tùy) với phương pháp 2 (biên dịch một lần). Tôi không thể tưởng tượng một nhà phát triển JS, những người sẽ vượt qua mã viết quản chế của họ theo cách như vậy.

Cách tiếp cận thực tế hơn rất nhiều là phân công ẩn danh, như trên thực tế bạn đang sử dụng cho phương thức document.onclick của bạn giống như sau, điều này thực sự nhẹ nhàng ủng hộ phương thức anon.

Sử dụng một khuôn khổ kiểm tra tương tự như của bạn:


function test(m) 
{ 
    for (var i = 0; i < 1000000; ++i) 
    { 
     m(); 
    } 
} 

function named() {var x = 0; x++;} 

var test1 = named; 

var test2 = function() {var x = 0; x++;} 

document.onclick = function() { 
    var start = new Date(); 
    test(test1); 
    var mid = new Date(); 
    test(test2); 
    var end = new Date(); 
    alert ("Test 1: " + (mid - start) + "ms\n Test 2: " + (end - mid) + "ms"); 
} 
0

tham chiếu gần như luôn luôn chậm hơn sau đó tham chiếu. Hãy suy nghĩ về nó theo cách này - giả sử bạn muốn in kết quả của việc thêm 1 + 1. Trong đó có ý nghĩa hơn:

alert(1 + 1); 

hoặc

a = 1; 
b = 1; 
alert(a + b); 

Tôi nhận ra đó là một cách thực sự đơn giản nhìn vào nó, nhưng nó minh họa, phải không? Sử dụng một tham chiếu chỉ nếu nó sẽ được sử dụng nhiều lần - ví dụ, trong đó các ví dụ này có ý nghĩa hơn:

$(a.button1).click(function(){alert('you clicked ' + this);}); 
$(a.button2).click(function(){alert('you clicked ' + this);}); 

hoặc

function buttonClickHandler(){alert('you clicked ' + this);} 
$(a.button1).click(buttonClickHandler); 
$(a.button2).click(buttonClickHandler); 

Điều thứ hai là thực hành tốt hơn, thậm chí nếu nó có nhiều dòng hơn. Hy vọng rằng tất cả điều này là hữu ích. (và cú pháp jquery không ném ai ra)

0

CÓ!Các hàm ẩn danh nhanh hơn các hàm thông thường. Có lẽ nếu tốc độ là điều quan trọng nhất ... quan trọng hơn việc sử dụng lại mã thì hãy xem xét sử dụng các hàm ẩn danh.

Có một bài viết thực sự tốt về tối ưu hóa javascript và chức năng ẩn danh ở đây:

http://dev.opera.com/articles/view/efficient-javascript/?page=2

0

@nickf

(ước gì có đại diện để chỉ bình luận, nhưng tôi đã chỉ mới tìm thấy trang web này)

Điểm của tôi là có sự nhầm lẫn ở đây giữa các hàm được đặt tên/ẩn danh và trường hợp sử dụng thực thi + biên dịch trong một lần lặp. Như tôi đã minh họa, sự khác biệt giữa anon + có tên là không đáng kể trong chính nó - Tôi đang nói đó là trường hợp sử dụng bị lỗi. Có vẻ như rõ ràng với tôi, nhưng nếu không, tôi nghĩ lời khuyên tốt nhất là "đừng làm những điều ngu ngốc" (trong đó khối liên tục dịch chuyển + tạo đối tượng của trường hợp sử dụng này là một) và nếu bạn không chắc chắn, kiểm tra!

1

Đối tượng ẩn danh nhanh hơn đối tượng được đặt tên. Nhưng gọi nhiều chức năng hơn là tốn kém hơn, và đến một mức độ làm lu mờ bất kỳ khoản tiết kiệm nào bạn có thể nhận được từ việc sử dụng các hàm ẩn danh. Mỗi chức năng được gọi là thêm vào các ngăn xếp cuộc gọi, trong đó giới thiệu một số lượng nhỏ nhưng không tầm thường của chi phí. Tuy nhiên, trừ khi bạn đang viết các thói quen mã hóa/giải mã hoặc một cái gì đó tương tự như nhạy cảm với hiệu suất, như nhiều người khác đã lưu ý nó luôn luôn tốt hơn để tối ưu hóa cho thanh lịch, dễ đọc mã trên mã nhanh chóng. Chúc vui vẻ!

Giả sử bạn đang viết mã có kiến ​​trúc tốt, thì vấn đề về tốc độ phải là trách nhiệm của những người viết phiên dịch/trình biên dịch.

1

Nơi chúng tôi có thể có tác động hiệu suất là trong hoạt động của các hàm khai báo. Dưới đây là một chuẩn mực của tuyên bố chức năng bên trong bối cảnh của một hàm hay bên ngoài:

http://jsperf.com/function-context-benchmark

Trong Chrome hoạt động được nhanh hơn nếu chúng ta khai báo hàm bên ngoài, nhưng trong Firefox đó là điều ngược lại.

Trong ví dụ khác, chúng tôi thấy rằng nếu các chức năng bên trong không phải là một chức năng thuần túy, nó sẽ có một thiếu hiệu quả cũng trong Firefox: http://jsperf.com/function-context-benchmark-3

0

Như đã chỉ ra trong các ý kiến ​​để @nickf câu trả lời: Câu trả lời để

là tạo ra một chức năng một lần nhanh hơn so với việc tạo ra nó một triệu lần

chỉ đơn giản là có. Nhưng như những chương trình của anh ấy, nó không chậm hơn một triệu, cho thấy nó thực sự nhanh hơn theo thời gian.

Câu hỏi thú vị hơn với tôi là:

thế nào để một lặp lại tạo + chạy so sánh để tạo ra một lần + lặp đi lặp lại chạy.

Nếu một hàm thực hiện tính toán phức tạp thì thời gian tạo đối tượng hàm có nhiều khả năng không đáng kể. Nhưng điều gì về người đứng đầu trên tạo trong trường hợp chạy là nhanh? Ví dụ:

// Variant 1: create once 
function adder(a, b) { 
    return a + b; 
} 
for (var i = 0; i < 100000; ++i) { 
    var x = adder(412, 123); 
} 

// Variant 2: repeated creation via function statement 
for (var i = 0; i < 100000; ++i) { 
    function adder(a, b) { 
    return a + b; 
    } 
    var x = adder(412, 123); 
} 

// Variant 3: repeated creation via function expression 
for (var i = 0; i < 100000; ++i) { 
    var x = (function(a, b) { return a + b; })(412, 123); 
} 

JS Perf Điều này cho thấy rằng việc tạo các chức năng chỉ một lần là nhanh như mong đợi. Tuy nhiên, ngay cả với một hoạt động rất nhanh chóng như một bổ sung đơn giản, chi phí của việc tạo ra các chức năng liên tục chỉ là một vài phần trăm.

Sự khác biệt có thể chỉ trở nên quan trọng trong trường hợp tạo đối tượng hàm phức tạp, trong khi duy trì thời gian chạy không đáng kể, ví dụ: nếu toàn bộ hàm chức năng được bao bọc thành if (unlikelyCondition) { ... }.

Các vấn đề liên quan