it-swarm-vi.tech

Việc sử dụng các chức năng ẩn danh có ảnh hưởng đến hiệu suất?

Tôi đã tự hỏi, 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? 

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

đấu với

function myEventHandler() {
    // do something
}

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

Đầu tiên là gọn gàng hơn vì nó không làm lộn xộn mã của bạn với các hàm hiếm khi được sử dụng, nhưng vấn đề là bạn có khai báo lại hàm đó nhiều lần không?

80
nickf

Vấn đề về hiệu năng ở đây là chi phí tạo 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à bạn sử dụng hàm ẩn danh:

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

Bạn đang tạo một ngàn đối tượng hàm riêng biệt mặc dù chúng có cùng một mã và không có ràng buộc với phạm vi từ vựng ( bao đóng ). Mặt khác, dường như nhanh hơn, bởi vì nó chỉ gán tham chiếu hàm same cho 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 hàm ẩn danh trước khi vào vòng lặp, thì chỉ gán tham chiếu cho nó cho các thành phần mảng trong khi bên trong vòng lặp, bạn sẽ thấy rằng không có sự khác biệt về hiệu năng hoặc ngữ nghĩa nào khi so sánh với phiên bản hàm được đặt 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í hiệu năng có thể quan sát được để sử dụng ẩn danh trên các chức năng được đặt tên.

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

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

và:

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

Cái trước là một khai báo hàm trong khi cái sau là một phép gán biến cho một hàm ẩn danh. Mặc dù chúng có vẻ có tác dụng tương tự, nhưng JavaScript lại đối xử với chúng hơi khác nhau. Để hiểu sự khác biệt, tôi khuyên bạn nên đọc, mập mơ hồ khai báo hàm JavaScript .

Thời gian thực hiện thực tế cho bất kỳ cách tiếp cận nào phần lớn sẽ được quyết định bởi việc trình duyệt 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 về hiệu suất trình duyệt hiện đại, hãy truy cập trang web JS Perf

81
Atif Aziz

Đây là mã kiểm tra 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ả:
Thử nghiệm 1: 142ms Thử nghiệm 2: 1983ms

Dường như cô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.

21
nickf

Theo nguyên tắc thiết kế chung, bạn nên tránh cấy mã giống nhau nhiều lần. Thay vào đó, bạn nên nâng mã chung vào một hàm và thực thi hàm đó (nói chung, được kiểm tra tốt, dễ sửa đổi) từ nhiều nơi.

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

Đây là một tính năng rất hữu ích trong các trường hợp cụ thể, nhưng không nên được sử dụng trong nhiều tình huống.

2
Tom Leys

Nơi chúng ta có thể có một tác động hiệu suất là trong hoạt động khai báo các hàm. Dưới đây là điểm chuẩn của việc khai báo các hàm bên trong ngữ cảnh của hàm khác hoặc bên ngoài:

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

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

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

1
Pablo Estornut

Tôi sẽ không mong đợi nhiều sự khác biệt nhưng nếu có một cái thì nó có thể sẽ thay đổi bởi công cụ kịch bản hoặc trình duyệt. 

Nếu bạn thấy mã dễ dàng hơn để tìm kiếm, hiệu suất không phải là vấn đề trừ khi bạn muốn gọi hàm hàng triệu lần.

1
Joe Skora

Các đối tượng ẩn danh nhanh hơn các đối tượng được đặt tên. Nhưng việc gọi nhiều chức năng thì tốn kém hơn và ở một mức độ làm lu mờ mọi khoản tiết kiệm bạn có thể nhận được từ việc sử dụng các chức năng ẩn danh. Mỗi chức năng được gọi là thêm vào ngăn xếp cuộc gọi, giới thiệu một lượng chi phí nhỏ nhưng không tầm thường.

Nhưng 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ạy cảm với hiệu suất, như nhiều người khác đã lưu ý rằng luôn luôn tốt hơn để tối ưu hóa cho mã thanh lịch, dễ đọc hơn mã nhanh.

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

1
pcorcoran

@nickf

Tuy nhiên, đó là một thử nghiệm khá mệt mỏi, bạn đang so sánh việc thực hiện và biên dịch thời gian rõ ràng là sẽ sử dụng phương pháp 1 (biên dịch N lần, tùy thuộc vào công cụ JS) 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 sẽ vượt qua mã viết thử việc của họ theo cách như vậy.

Một cách tiếp cận thực tế hơn nhiều là gán ẩn danh, vì trên thực tế, bạn đang sử dụng cho phương thức document.onclick của mình giống như sau, trong thực tế, nó rất thích phương pháp anon.

Sử dụng một khung 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
annakata

@nickf

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

Quan đ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 tên là không đáng kể - tôi đang nói đó là trường hợp sử dụng bị lỗi.

Điều này có vẻ hiển nhiên đối 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 điều ngu ngốc" (trong đó việc thay đổi khối liên tục + 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, hãy kiểm tra!

0
annakata

VÂNG! Các chức năng ẩn danh nhanh hơn các chức năng thông thường. Có lẽ nếu tốc độ là 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ự hay về tối ưu hóa các hàm javascript và ẩn danh ở đây:

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

0
Christopher Tokar

một tài liệu tham khảo gần như luôn luôn sẽ chậm hơn sau đó là điều mà nó đề cập đến. Hãy 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. Điều này có ý nghĩa hơn:

alert(1 + 1);

hoặc là

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

Tôi nhận ra rằng đó là một cách thực sự đơn giản để xem xét nó, nhưng nó mang tính minh họa, phải không? Chỉ sử dụng một tham chiếu nếu nó sẽ được sử dụng nhiều lần - ví dụ, ví dụ nào trong số những 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 là

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

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

0
matt lohkamp

Như đã chỉ ra trong các bình luận cho câu trả lời @nickf: Câu trả lời cho 

Tạo một hàm nhanh hơn một lần so với tạo một triệu lần

chỉ đơn giản là có. Nhưng như sự hoàn hảo về JS của anh ấy cho thấy, nó không chậm hơn với hệ số một triệu, cho thấy rằng nó thực sự trở nên nhanh hơn theo thời gian.

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

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

Nếu một hàm thực hiện một phép tính phức tạp, thời gian để tạo đối tượng hàm rất có thể không đáng kể. Nhưng những gì về phần đầu của tạo trong trường hợp run 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);
}

Điều này JS Perf cho thấy việc tạo hàm chỉ một lần nhanh hơn mong đợi. Tuy nhiên, ngay cả với một thao tác rất nhanh như một add đơn giản, chi phí tạo ra chức năng lặp đi lặp lại chỉ là một vài phần trăm.

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

0
bluenote10

Điều chắc chắn sẽ làm cho vòng lặp của bạn nhanh hơn trên nhiều trình duyệt, đặc biệt là các trình duyệt IE, đang lặp như sau:

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

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

0
Sarhanis