Hàm (function () {}) () hoạt động như thế nào và tại sao mọi người sử dụng nó?


79

(function() {})()và người anh em họ cụ thể của jQuery của nó luôn (function($) {})(jQuery)bật lên trong mã Javascript.

Các cấu trúc này hoạt động như thế nào và chúng giải quyết những vấn đề gì?

Các ví dụ được đánh giá cao



2
Nó không phải là một bản sao chính xác. Câu trả lời của những câu hỏi khác không thực sự nói lên lý do tại sao một câu trả lời lại jQuerylà một đối số.
Georg Schölly

1
Tôi luôn tự hỏi tại sao lại cần thêm dấu ngoặc đơn. Tôi tin rằng nó liên quan đến sự không rõ ràng giữa các khối và các ký tự đối tượng với các dấu ngoặc nhọn. Lưu ý rằng chúng không bắt buộc nếu bạn đang gán cho một thứ gì đó, chẳng hạn như var x = function () {} () hoạt động tốt.
jpsimons

1
Tại sao điều này lại là "Câu hỏi được đóng lại không phải là một câu hỏi thực"? Câu hỏi rõ ràng và có liên quan.
Chris

Câu trả lời:


73

Với sự phổ biến ngày càng tăng của các khung JavaScript, $dấu hiệu đã được sử dụng trong nhiều trường hợp khác nhau. Vì vậy, để giảm bớt xung đột có thể xảy ra, bạn có thể sử dụng các cấu trúc sau:

(function ($){
  // Your code using $ here.
})(jQuery);

Cụ thể, đó là một khai báo hàm ẩn danh được thực thi ngay lập tức truyền đối tượng jQuery chính làm tham số. Bên trong hàm đó, bạn có thể sử dụng $để tham chiếu đến đối tượng đó mà không cần lo lắng về các khuôn khổ khác cũng nằm trong phạm vi.


4
Câu trả lời ngắn gọn và ý nghĩa. +1!
pestaa

5
Về mặt kỹ thuật, nó là một biểu thức hàm (khai báo!). IIFE
John Strickler

Bạn có thể giải thích ý bạn là: "... mà không cần lo lắng về các khung công tác khác cũng nằm trong phạm vi."
Redtopia

@Redtopia. Phạm vi Javascript được xác định ở cấp hàm. Mọi thứ bên ngoài một chức năng đều ở phạm vi toàn cầu. Hãy nghĩ xem có bao nhiêu Javascript được đưa vào một trang web. Mỗi biến bên ngoài một phạm vi hàm đều có sự va chạm khéo léo.
Alberto De Caro,

Cảm ơn bạn đã làm rõ ... tôi không rõ ràng rằng bạn đang đề cập đến phạm vi cục bộ của hàm ẩn danh, không thể nhìn thấy phạm vi này từ phạm vi toàn cục hoặc phạm vi của bất kỳ hàm nào khác.
Redtopia

41

Đây là một kỹ thuật được sử dụng để giới hạn phạm vi biến; đó là cách duy nhất để ngăn các biến gây ô nhiễm không gian tên chung.

var bar = 1; // bar is now part of the global namespace
alert(bar);

(function () {
   var foo = 1; // foo has function scope
   alert(foo); 
   // code to be executed goes here
})();

7
... và vẫn có chúng là "toàn cầu" đối với mã của bạn.
tvanfosson

Vì vậy, tham chiếu đến Opera trong mã ví dụ là không liên quan / sai?
Bobby Jack

1
@Bobby: có thể có các biến / hàm bổ sung trong khối đó chỉ được sử dụng cho Opera.
geowa4

19

1) Nó xác định một hàm ẩn danh và thực thi nó ngay lập tức.

2) Nó thường được thực hiện để không làm ô nhiễm không gian tên chung với mã không mong muốn.

3) Bạn cần hiển thị một số phương thức từ nó, bất kỳ thứ gì được khai báo bên trong sẽ là "riêng tư", ví dụ:

MyLib = (function(){
    // other private stuff here
    return {
        init: function(){
        }
    };

})();

Hay cách khác:

MyLib = {};
(function({
    MyLib.foo = function(){
    }
}));

Vấn đề là, có nhiều cách bạn có thể sử dụng, nhưng kết quả vẫn không đổi.


1. Một chức năng như vậy được định nghĩa trong một tệp được tách biệt, nó thực thi như thế nào? (Có vẻ như không ai gọi hàm này, vậy hàm được kích hoạt như thế nào?) 2. Tôi không thấy hàm ở cấp cao nhất có câu lệnh "return" ... Tôi đang đọc dojo.util._doh ._browserRunner.js Xin cảm ơn!
Paul

1) Như tôi đã nói, nó thực thi chính nó, dấu () cuối cùng yêu cầu nó thực thi. 2) Nó không nhất thiết phải có giá trị trả về, nó cũng có thể đặt các giá trị trên một thuộc tính cụ thể. Nó chỉ là một ví dụ tôi cập nhật câu trả lời với một khả năng khác.
Evan Trimboli

9

Nó chỉ là một chức năng ẩn danh được gọi ngay lập tức. Trước tiên, bạn có thể tạo một hàm và sau đó gọi nó, và bạn sẽ có được hiệu ứng tương tự:

(function(){ ... })();

hoạt động như:

temp = function(){ ... };
temp();

Bạn cũng có thể làm tương tự với một hàm được đặt tên:

function temp() { ... }
temp();

Mã mà bạn gọi là jQuery-cụ thể chỉ có nghĩa là bạn sử dụng đối tượng jQuery trong đó. Nó chỉ là một hàm ẩn danh với một tham số, được gọi ngay lập tức.

Bạn có thể làm điều tương tự trong hai bước và bạn có thể làm điều đó với bất kỳ thông số nào bạn thích:

temp = function(answer){ ... };
temp(42);

Vấn đề mà điều này giải quyết là nó tạo ra một bao đóng cho mã trong hàm. Bạn có thể khai báo các biến trong đó mà không làm ô nhiễm không gian tên chung, do đó giảm nguy cơ xung đột khi sử dụng tập lệnh này cùng với tập lệnh khác.

Trong trường hợp cụ thể đối với jQuery, bạn sử dụng nó ở chế độ tương thích, nơi nó không khai báo tên $ làm bí danh cho jQuery. Bằng cách gửi đối tượng jQuery vào trong bao đóng và đặt tên cho tham số $, bạn vẫn có thể sử dụng cú pháp tương tự như khi không có chế độ tương thích.


Như tôi đã nhận xét về câu trả lời của @ spoulson, một hàm ẩn danh không giống như một hàm đóng.
Tim Down

7

Nó giải thích ở đây rằng cấu trúc đầu tiên của bạn cung cấp phạm vi cho các biến.

Các biến được xác định phạm vi ở cấp hàm trong javascript. Điều này khác với những gì bạn có thể quen dùng trong một ngôn ngữ như C # hoặc Java, nơi các biến được xác định phạm vi cho khối. Điều này có nghĩa là nếu bạn khai báo một biến bên trong một vòng lặp hoặc một câu lệnh if, nó sẽ có sẵn cho toàn bộ hàm.

Nếu bạn thấy mình cần xác định phạm vi rõ ràng cho một biến bên trong một hàm, bạn có thể sử dụng một hàm ẩn danh để thực hiện việc này. Bạn thực sự có thể tạo một hàm ẩn danh và sau đó thực thi nó ngay lập tức và tất cả các biến bên trong sẽ được chuyển thành hàm ẩn danh:

(function() {
  var myProperty = "hello world";
  alert(myProperty);
})();
alert(typeof(myProperty)); // undefined

6

Một lý do khác để làm điều này là để loại bỏ bất kỳ sự nhầm lẫn nào về $nhà điều hành của khuôn khổ mà bạn đang sử dụng. Ví dụ: để buộc jQuery, bạn có thể làm:

;(function($){
   ... your jQuery code here...
})(jQuery);

Bằng cách truyền vào $toán tử dưới dạng một tham số và gọi nó trên jQuery, $toán tử trong hàm sẽ bị khóa với jQuery ngay cả khi bạn đã tải các khung công tác khác.


Đó là một biến thể cụ thể hơn cho câu hỏi ban đầu, nhưng nó vẫn là thông tin tốt! :)
Bobby Jack

Không phải là một câu trả lời, nhưng tôi đã nâng cao nó cho ý tưởng tuyệt vời mà bạn có!
Spidey

5

Một cách sử dụng khác cho cấu trúc này là "nắm bắt" các giá trị của các biến cục bộ sẽ được sử dụng trong một bao đóng. Ví dụ:

for (var i = 0; i < 3; i++) {
    $("#button"+i).click(function() {
        alert(i);
    });
}

Đoạn mã trên sẽ làm cho cả ba nút bật lên "3". Mặt khác:

for (var i = 0; i < 3; i++) {
    (function(i) {
        $("#button"+i).click(function() {
            alert(i);
        });
    })(i);
}

Điều này sẽ làm cho ba nút bật lên "0", "1" và "2" như mong đợi.

Lý do cho điều này là một bao đóng giữ một tham chiếu đến khung ngăn xếp bao quanh của nó , khung này giữ các giá trị hiện tại của các biến của nó. Nếu các biến đó thay đổi trước khi quá trình đóng thực thi, thì quá trình đóng sẽ chỉ thấy các giá trị mới nhất, không phải các giá trị như chúng ở thời điểm đóng được tạo. Bằng cách gói việc tạo bao đóng bên trong một hàm khác như trong ví dụ thứ hai ở trên, giá trị hiện tại của biến iđược lưu trong khung ngăn xếp của hàm ẩn danh.


Một nitpick nhỏ, nhưng nó không phải là khung ngăn xếp được đóng lại, nó là các phạm vi từ vựng xung quanh. Việc triển khai tốt các bao đóng sẽ chỉ nắm bắt các biểu tượng thực sự được đề cập bên trong bao đóng, một số triển khai chỉ đơn giản là nắm bắt mọi thứ (Ruby làm điều này, vì vậy hãy cẩn thận với những gì bạn có gần các bao đóng). Một cách để suy nghĩ về một đối tượng trong OOP là như một bao đóng nơi các ký hiệu được chụp được xác định rõ ràng (các thuộc tính thành viên của đối tượng).
KayEss

Điều đó đúng, nhưng đó thực sự chỉ là một vấn đề triển khai và về mặt khái niệm, người ta có thể coi rằng khung ngăn xếp là những gì được tham chiếu bởi bao đóng. Các "đối tượng" kiểu OOP trong các ngôn ngữ họ Lisp đều được thực hiện với các bao đóng, đó là một kỹ thuật mạnh mẽ.
Greg Hewgill

Có, mặc dù nếu bạn muốn suy nghĩ về các khung ngăn xếp thì bao đóng là một chuỗi các khung ngăn xếp, một khung cho mỗi hàm bao trong phạm vi từ vựng cùng với phạm vi toàn cục. Phạm vi từ vựng dễ nghĩ hơn (đặc biệt là khi giải thích cho ai đó chưa quen với bao đóng) vì bạn không vướng vào một cuộc thảo luận vô nghĩa (và gây hiểu lầm) về khung ngăn xếp nào khi bạn có nhiều cấp hàm lồng nhau. Phạm vi từ vựng chỉ đơn giản là những gì bạn thấy trong mã nguồn.
KayEss

4

Đây được coi là một sự đóng cửa . Nó có nghĩa là mã được chứa sẽ chạy trong phạm vi từ vựng của riêng nó. Điều này có nghĩa là bạn có thể xác định các biến và hàm mới và chúng sẽ không xung đột với vùng tên được sử dụng trong mã bên ngoài bao đóng.

var i = 0;
alert("The magic number is " + i);

(function() {
   var i = 99;
   alert("The magic number inside the closure is " + i);
})();

alert("The magic number is still " + i);

Điều này sẽ tạo ra ba cửa sổ bật lên, chứng tỏ rằng itrong bao đóng không làm thay đổi biến có cùng tên đã tồn tại trước đó:

  • Con số kỳ diệu là 0
  • Con số kỳ diệu bên trong sự đóng cửa là 99
  • Con số kỳ diệu vẫn là 0

5
Đó không phải là một bao đóng, điều đó chỉ đơn giản là chứng minh rằng một hàm có phạm vi riêng của nó. Bao đóng được hình thành khi một hàm được xác định bên trong một hàm khác và trở nên khả dụng bên ngoài hàm đó, do đó vẫn giữ được quyền truy cập của nó vào các biến, hàm và tham số trong hàm bên ngoài ngay cả sau khi hàm bên ngoài đó đã trả về.
Tim Down

Chỉ cần thay đổi "(function () {..." thành "var func = function () {...}; func ();" Ở đó, đóng cửa..
spoulson

Tôi không muốn làm việc này, nhưng ... vẫn không kết thúc: không có chức năng bên trong nào được tạo ra bên ngoài chức năng bên ngoài. Một cách dễ dàng để tạo một bao đóng trong ví dụ của bạn là trả về một hàm đã cảnh báo i: var f = (function () {var i = 99; return function () {alert (i);};}) (); f ();
Tim Down

Điểm lưu ý. Nó đi đến các chi tiết cụ thể của việc thực hiện.
spoulson

2

Chúng thường được sử dụng trong các plugin jQuery. Như đã giải thích trong Hướng dẫn tác giả jQuery Plugins, tất cả các biến được khai báo bên trong { }đều là riêng tư và không hiển thị ra bên ngoài, điều này cho phép đóng gói tốt hơn.


3
{}không tự tạo ra một phạm vi mới trong JavaScript. Phạm vi được tạo bởi khai báo hàm.
Annabelle

2

Như những người khác đã nói, cả hai đều xác định các hàm ẩn danh được gọi ngay lập tức. Tôi thường gói các khai báo lớp JavaScript của mình trong cấu trúc này để tạo phạm vi riêng tư tĩnh cho lớp. Sau đó, tôi có thể đặt dữ liệu hằng số, phương thức tĩnh, trình xử lý sự kiện hoặc bất kỳ thứ gì khác trong phạm vi đó và nó sẽ chỉ hiển thị với các phiên bản của lớp:

// Declare a namespace object.
window.MyLibrary = {};

// Wrap class declaration to create a private static scope.
(function() {
  var incrementingID = 0;

  function somePrivateStaticMethod() {
    // ...
  }

  // Declare the MyObject class under the MyLibrary namespace.
  MyLibrary.MyObject = function() {
    this.id = incrementingID++;
  };

  // ...MyObject's prototype declaration goes here, etc...
  MyLibrary.MyObject.prototype = {
    memberMethod: function() {
      // Do some stuff
      // Maybe call a static private method!
      somePrivateStaticMethod();
    }
  };
})();

Trong ví dụ này, MyObjectlớp được gán cho MyLibrarykhông gian tên, vì vậy nó có thể truy cập được. incrementingIDsomePrivateStaticMethod()không thể truy cập trực tiếp bên ngoài phạm vi chức năng ẩn danh.


2

Về cơ bản, đó là không gian tên mã JavaScript của bạn.

Ví dụ: bạn có thể đặt bất kỳ biến hoặc hàm nào bên trong đó và từ bên ngoài, chúng không tồn tại trong phạm vi đó. Vì vậy, khi bạn gói gọn mọi thứ trong đó, bạn không phải lo lắng về các cuộc đụng độ.

()cuối có nghĩa là để tự gọi. Bạn cũng có thể thêm một đối số ở đó sẽ trở thành đối số của hàm ẩn danh của bạn. Tôi làm điều này với jQuery thường xuyên và bạn có thể hiểu tại sao ...

(function($) {

    // Now I can use $, but it won't affect any other library like Prototype
})(jQuery);

Evan Trimboli bao gồm phần còn lại trong câu trả lời của mình .


1

Đó là một chức năng tự gọi. Kiểu như viết tắt

function DoSomeStuff($)
{
}

DoSomeStuff(jQuery);

1

Những gì đoạn mã trên đang làm là tạo một hàm ẩn danh trên dòng 1 và sau đó gọi nó ở dòng 3 với 0 đối số. Điều này đóng gói hiệu quả tất cả các hàm và biến được xác định trong thư viện đó, bởi vì tất cả các hàm sẽ chỉ có thể truy cập được bên trong hàm ẩn danh đó.

Đây là một thực tiễn tốt, và lý do đằng sau nó là để tránh làm ô nhiễm không gian tên toàn cầu bằng các biến và hàm, có thể bị che bởi các phần Javascript khác trên toàn trang web.

Để làm rõ cách gọi hàm, hãy xem xét ví dụ đơn giản:

Nếu bạn có một dòng Javascript này được bao gồm, nó sẽ tự động gọi mà không cần gọi nó một cách rõ ràng:

alert('hello');

Vì vậy, hãy lấy ý tưởng đó và áp dụng nó vào ví dụ sau:

(function() {
    alert('hello')
    //anything I define in here is scoped to this function only
}) (); //here, the anonymous function is invoked

Kết quả cuối cùng là tương tự, vì hàm ẩn danh được gọi giống như ví dụ trước.


xin vui lòng xem câu hỏi đầu tiên của tôi về các ý kiến ​​cho Evan ... Cảm ơn!
Paul

//here, the anonymous function is invoked, Đơn giản, nhưng tôi không nhìn thấy nó trước :)
Stefan

1

Bởi vì các câu trả lời tốt đã được thực hiện :) Tôi sẽ đưa ra một gợi ý để xem một số video John Resig video 1 , video 2 (người phát minh ra jQuery & bậc thầy về JavaScript).

Một số thông tin chi tiết và câu trả lời thực sự tốt được cung cấp trong video.

Đó là những gì tôi đã làm vào lúc tôi nhìn thấy câu hỏi của bạn.


0
function(){ // some code here }

là cách để định nghĩa một hàm ẩn danh trong javascript. Chúng có thể cung cấp cho bạn khả năng thực thi một hàm trong ngữ cảnh của một hàm khác (nếu không thì bạn có thể không có khả năng đó).

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.