Mục đích của việc gói toàn bộ tệp Javascript trong các hàm ẩn danh như là ((hàm () {,}}) (), là gì?


584

Gần đây tôi đã đọc rất nhiều Javascript và tôi đã nhận thấy rằng toàn bộ tệp được gói như sau trong các tệp .js sẽ được nhập.

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

Lý do để làm điều này hơn là một tập hợp các hàm xây dựng đơn giản là gì?


6
Vì tôi tưởng tượng điều này sẽ được sử dụng bởi rất nhiều người, xin đừng quên việc đóng cửa;
dGH

5
Kỹ thuật này được gọi là "IIFE" tôi nghĩ. Chữ này là viết tắt của Biểu thức chức năng được gọi ngay lập tức en.wikipedia.org/wiki/Immediately-invoking_feft_expression
Adrien Be

Câu trả lời:


786

Nó thường là không gian tên (xem sau) và kiểm soát mức độ hiển thị của các hàm thành viên và / hoặc các biến. Hãy nghĩ về nó giống như một định nghĩa đối tượng. Tên kỹ thuật của nó là Biểu thức hàm được gọi ngay lập tức (IIFE). Các plugin jQuery thường được viết như thế này.

Trong Javascript, bạn có thể lồng các hàm. Vì vậy, sau đây là hợp pháp:

function outerFunction() {
   function innerFunction() {
      // code
   }
}

Bây giờ bạn có thể gọi outerFunction(), nhưng độ nhớt của innerFunction()giới hạn trong phạm vi outerFunction(), có nghĩa là nó là riêng tư outerFunction(). Về cơ bản, nó tuân theo nguyên tắc giống như các biến trong Javascript:

var globalVariable;

function someFunction() {
   var localVariable;
}

Tương ứng:

function globalFunction() {

   var localFunction1 = function() {
       //I'm anonymous! But localFunction1 is a reference to me!
   };

   function localFunction2() {
      //I'm named!
   }
}

Trong trường hợp trên, bạn có thể gọi globalFunction()từ bất cứ đâu, nhưng bạn không thể gọi localFunction1hoặc localFunction2.

Những gì bạn đang làm khi bạn viết (function() { ... })(), là bạn đang tạo mã bên trong bộ dấu ngoặc đơn đầu tiên một hàm theo nghĩa đen (có nghĩa là toàn bộ "đối tượng" thực sự là một hàm). Sau đó, bạn tự gọi hàm (cuối cùng ()) mà bạn vừa xác định. Vì vậy, ưu điểm chính của điều này như tôi đã đề cập trước đây, là bạn có thể có các phương thức / hàm và thuộc tính riêng tư:

(function() {
   var private_var;

   function private_function() {
     //code
   }
})();

Trong ví dụ đầu tiên, bạn sẽ gọi một cách rõ ràng globalFunctionbằng tên để chạy nó. Đó là, bạn sẽ chỉ làm globalFunction()để chạy nó. Nhưng trong ví dụ trên, bạn không chỉ xác định hàm; bạn đang xác định gọi nó trong một lần. Điều này có nghĩa là khi tệp JavaScript của bạn được tải, nó sẽ được thực thi ngay lập tức. Tất nhiên, bạn có thể làm:

function globalFunction() {
    // code
}
globalFunction();

Hành vi phần lớn sẽ giống nhau ngoại trừ một điểm khác biệt đáng kể: bạn tránh gây ô nhiễm phạm vi toàn cầu khi bạn sử dụng IIFE (do đó, điều đó cũng có nghĩa là bạn không thể gọi hàm nhiều lần vì nó không có tên, nhưng vì chức năng này chỉ có nghĩa là được thực thi khi nó thực sự không phải là vấn đề).

Điều thú vị với IIFE là bạn cũng có thể xác định những thứ bên trong và chỉ để lộ những phần bạn muốn ra thế giới bên ngoài (ví dụ về không gian tên để bạn có thể tạo thư viện / plugin của riêng mình):

var myPlugin = (function() {
 var private_var;

 function private_function() {
 }

 return {
    public_function1: function() {
    },
    public_function2: function() {
    }
 }
})()

Bây giờ bạn có thể gọi myPlugin.public_function1(), nhưng bạn không thể truy cập private_function()! Vì vậy, khá giống với một định nghĩa lớp. Để hiểu rõ hơn về điều này, tôi khuyên bạn nên sử dụng các liên kết sau để đọc thêm:

BIÊN TẬP

Tôi đã quên không đề cập. Trong trận chung kết đó (), bạn có thể vượt qua bất cứ điều gì bạn muốn bên trong. Ví dụ: khi bạn tạo các trình cắm jQuery, bạn chuyển vào jQueryhoặc $tương tự như vậy:

(function(jQ) { ... code ... })(jQuery) 

Vì vậy, những gì bạn đang làm ở đây là xác định một hàm có một tham số (được gọi là jQbiến cục bộ và chỉ được biết đến với hàm đó). Sau đó, bạn tự gọi hàm và truyền vào một tham số (còn được gọi jQuery, nhưng hàm này là từ thế giới bên ngoài và tham chiếu đến chính jQuery thực tế). Không cần phải làm điều này, nhưng có một số lợi thế:

  • Bạn có thể xác định lại một tham số toàn cầu và đặt cho nó một tên có ý nghĩa trong phạm vi địa phương.
  • Có một lợi thế hiệu suất nhỏ vì nó nhanh hơn để tìm kiếm mọi thứ trong phạm vi địa phương thay vì phải đi lên chuỗi phạm vi vào phạm vi toàn cầu.
  • Có lợi ích cho việc nén (thu nhỏ).

Trước đó tôi đã mô tả làm thế nào các chức năng này chạy tự động khi khởi động, nhưng nếu chúng chạy tự động thì ai sẽ chuyển qua các đối số? Kỹ thuật này giả định rằng tất cả các tham số bạn cần đã được xác định là biến toàn cục. Vì vậy, nếu jQuery chưa được định nghĩa là biến toàn cục thì ví dụ này sẽ không hoạt động. Như bạn có thể đoán, một điều jquery.js thực hiện trong quá trình khởi tạo của nó là xác định biến toàn cầu 'jQuery', cũng như biến toàn cục '$' nổi tiếng hơn của nó, cho phép mã này hoạt động sau khi jQuery được đưa vào.


14
Rất tuyệt, tôi hiểu rất rõ không gian tên, nhưng tôi đã thấy rất nhiều ví dụ cuối cùng của bạn và không thể hiểu được những gì mọi người đang cố gắng đạt được. Điều này thực sự xóa mọi thứ lên.
Andrew Kou

34
Bài đăng tuyệt vời. Cảm ơn rất nhiều.
Darren

4
Tôi nghĩ rằng thêm một dấu chấm phẩy hàng đầu và dấu ';' sẽ làm cho ví dụ hoàn tất - ;(function(jQ) { ... code ... })(jQuery);Bằng cách này nếu ai đó bỏ dấu chấm phẩy trong tập lệnh của họ, nó sẽ không phá vỡ tập lệnh của bạn, đặc biệt nếu bạn dự định thu nhỏ và ghép tập lệnh của bạn với tập lệnh khác.
Taras Alenin

3
bài đăng tốt đẹp, tôi thích sự nhấn mạnh vào các biến riêng tư. Tôi cũng thích việc mở mô hình / đóng cửa mô-đun (public_feft1 & public_feft2) & cách bạn chuyển các biến, mặc dù hơi vượt ra khỏi phạm vi đó là một giới thiệu hay. Tôi cũng đã thêm một câu trả lời, câu hỏi này tập trung vào những gì tôi cho là gốc rễ của cú pháp & sự khác biệt giữa câu lệnh hàm và biểu thức hàm & điều tôi nghĩ là "chỉ là một quy ước" so với "cách duy nhất để đạt được kết quả này".
Adrien Be

4
Bài đăng tuyệt vời, tôi nghĩ có lẽ nhiều hơn về cách chuyển các biến vào hàm tự thực thi là có lợi. Bối cảnh trong chức năng tự thực hiện là sạch - không có dữ liệu. Bạn có thể vượt qua trong bối cảnh bằng cách thực hiện điều này (function (context) { ..... })(this)sau đó cho phép bạn đính kèm bất cứ điều gì bạn thích vào bối cảnh cha mẹ do đó phơi bày nó.
Callum Linington

79

Nói ngắn gọn

Tóm lược

Ở dạng đơn giản nhất, kỹ thuật này nhằm mục đích bọc mã bên trong một phạm vi chức năng .

Nó giúp giảm cơ hội:

  • xung đột với các ứng dụng / thư viện khác
  • phạm vi gây ô nhiễm (rất có thể là toàn cầu)

không phát hiện khi tài liệu đã sẵn sàng - nó không phải là một loại document.onloadcũng khôngwindow.onload

Nó thường được gọi là một Immediately Invoked Function Expression (IIFE)hoặc Self Executing Anonymous Function.

Mã giải thích

var someFunction = function(){ console.log('wagwan!'); };

(function() {                   /* function scope starts here */
  console.log('start of IIFE');

  var myNumber = 4;             /* number variable declaration */
  var myFunction = function(){  /* function variable declaration */
    console.log('formidable!'); 
  };
  var myObject = {              /* object variable declaration */
    anotherNumber : 1001, 
    anotherFunc : function(){ console.log('formidable!'); }
  };
  console.log('end of IIFE');
})();                           /* function scope ends */

someFunction();            // reachable, hence works: see in the console
myFunction();              // unreachable, will throw an error, see in the console
myObject.anotherFunc();    // unreachable, will throw an error, see in the console

Trong ví dụ trên, bất kỳ biến nào được xác định trong hàm (nghĩa là được khai báo sử dụng var) sẽ là "riêng tư" và có thể truy cập trong phạm vi hàm CHỈ (như Vivin Paliath đặt nó). Nói cách khác, các biến này không thể nhìn thấy / có thể truy cập bên ngoài hàm. Xem bản demo trực tiếp .

Javascript có chức năng phạm vi. "Các tham số và biến được định nghĩa trong hàm không hiển thị bên ngoài hàm và rằng một biến được xác định ở bất kỳ đâu trong hàm có thể nhìn thấy ở mọi nơi trong hàm." (từ "Javascript: Bộ phận tốt").


Thêm chi tiết

Mã thay thế

Cuối cùng, mã được đăng trước đó cũng có thể được thực hiện như sau:

var someFunction = function(){ console.log('wagwan!'); };

var myMainFunction = function() {
  console.log('start of IIFE');

  var myNumber = 4;
  var myFunction = function(){ console.log('formidable!'); };
  var myObject = { 
    anotherNumber : 1001, 
    anotherFunc : function(){ console.log('formidable!'); }
  };
  console.log('end of IIFE');
};

myMainFunction();          // I CALL "myMainFunction" FUNCTION HERE
someFunction();            // reachable, hence works: see in the console
myFunction();              // unreachable, will throw an error, see in the console
myObject.anotherFunc();    // unreachable, will throw an error, see in the console

Xem bản demo trực tiếp .


Nhưng cai rê

Lặp lại 1

Một ngày nọ, có lẽ ai đó đã nghĩ rằng "phải có một cách để tránh đặt tên 'myMainFunction', vì tất cả những gì chúng ta muốn là thực hiện nó ngay lập tức."

Nếu bạn quay lại những điều cơ bản, bạn sẽ thấy rằng:

  • expression: một cái gì đó đánh giá đến một giá trị. I E3+11/x
  • statement: dòng (s) mã đang làm gì đó NHƯNG nó không đánh giá thành giá trị. I Eif(){}

Tương tự, các biểu thức hàm đánh giá một giá trị. Và một hậu quả (tôi giả sử?) Là chúng có thể được gọi ngay lập tức:

 var italianSayinSomething = function(){ console.log('mamamia!'); }();

Vì vậy, ví dụ phức tạp hơn của chúng tôi trở thành:

var someFunction = function(){ console.log('wagwan!'); };

var myMainFunction = function() {
  console.log('start of IIFE');

  var myNumber = 4;
  var myFunction = function(){ console.log('formidable!'); };
  var myObject = { 
    anotherNumber : 1001, 
    anotherFunc : function(){ console.log('formidable!'); }
  };
  console.log('end of IIFE');
}();

someFunction();            // reachable, hence works: see in the console
myFunction();              // unreachable, will throw an error, see in the console
myObject.anotherFunc();    // unreachable, will throw an error, see in the console

Xem bản demo trực tiếp .

Lặp lại 2

Bước tiếp theo là suy nghĩ "tại sao lại có var myMainFunction =nếu chúng ta thậm chí không sử dụng nó!?".

Câu trả lời rất đơn giản: hãy thử loại bỏ điều này, chẳng hạn như dưới đây:

 function(){ console.log('mamamia!'); }();

Xem bản demo trực tiếp .

Nó sẽ không hoạt động vì "khai báo hàm không thể xâm phạm" .

Thủ thuật là bằng cách loại bỏ var myMainFunction =chúng ta đã chuyển đổi biểu thức hàm thành khai báo hàm . Xem các liên kết trong "Tài nguyên" để biết thêm chi tiết về điều này.

Câu hỏi tiếp theo là "tại sao tôi không thể giữ nó như một biểu thức chức năng với cái gì khác hơn var myMainFunction =?

Câu trả lời là "bạn có thể", và thực tế có nhiều cách bạn có thể làm điều này: thêm a +, a !, a -hoặc có thể gói trong một cặp dấu ngoặc đơn (như bây giờ được thực hiện theo quy ước), và tôi tin hơn. Ví dụ như:

 (function(){ console.log('mamamia!'); })(); // live demo: jsbin.com/zokuwodoco/1/edit?js,console.

hoặc là

 +function(){ console.log('mamamia!'); }(); // live demo: jsbin.com/wuwipiyazi/1/edit?js,console

hoặc là

 -function(){ console.log('mamamia!'); }(); // live demo: jsbin.com/wejupaheva/1/edit?js,console

Vì vậy, một khi sửa đổi có liên quan được thêm vào cái đã từng là "Mã thay thế" của chúng tôi, chúng tôi sẽ quay lại mã chính xác giống như mã được sử dụng trong ví dụ "Giải thích mã"

var someFunction = function(){ console.log('wagwan!'); };

(function() {
  console.log('start of IIFE');

  var myNumber = 4;
  var myFunction = function(){ console.log('formidable!'); };
  var myObject = { 
    anotherNumber : 1001, 
    anotherFunc : function(){ console.log('formidable!'); }
  };
  console.log('end of IIFE');
})();

someFunction();            // reachable, hence works: see in the console
myFunction();              // unreachable, will throw an error, see in the console
myObject.anotherFunc();    // unreachable, will throw an error, see in the console

Đọc thêm về Expressions vs Statements:


Làm sáng tỏ phạm vi

Một điều người ta có thể tự hỏi là "điều gì xảy ra khi bạn KHÔNG định nghĩa biến 'đúng' bên trong hàm - tức là thực hiện một phép gán đơn giản?"

(function() {
  var myNumber = 4;             /* number variable declaration */
  var myFunction = function(){  /* function variable declaration */
    console.log('formidable!'); 
  };
  var myObject = {              /* object variable declaration */
    anotherNumber : 1001, 
    anotherFunc : function(){ console.log('formidable!'); }
  };
  myOtherFunction = function(){  /* oops, an assignment instead of a declaration */
    console.log('haha. got ya!');
  };
})();
myOtherFunction();         // reachable, hence works: see in the console
window.myOtherFunction();  // works in the browser, myOtherFunction is then in the global scope
myFunction();              // unreachable, will throw an error, see in the console

Xem bản demo trực tiếp .

Về cơ bản, nếu một biến không được khai báo trong phạm vi hiện tại của nó được gán một giá trị, thì "việc tra cứu chuỗi phạm vi xảy ra cho đến khi tìm thấy biến hoặc chạm vào phạm vi toàn cầu (tại đó nó sẽ tạo ra nó)".

Khi ở trong môi trường trình duyệt (so với môi trường máy chủ như nodejs), phạm vi toàn cục được xác định bởi windowđối tượng. Do đó chúng ta có thể làm window.myOtherFunction().

Mẹo "Thực hành tốt" của tôi về chủ đề này là luôn luôn sử dụng varkhi xác định bất cứ điều gì : cho dù đó là số, đối tượng hoặc chức năng, và ngay cả khi trong phạm vi toàn cầu. Điều này làm cho mã đơn giản hơn nhiều.

Ghi chú:

  • javascript khôngblock scope(Cập nhật: chặn các biến cục bộ phạm vi được thêm vào trong ES6 .)
  • javascript chỉ có function scope& global scope( windowphạm vi trong môi trường trình duyệt)

Đọc thêm về Javascript Scopes:


Tài nguyên


Bước tiếp theo

Khi bạn có được IIFEkhái niệm này , nó sẽ dẫn đến module pattern, thường được thực hiện bằng cách tận dụng mẫu IIFE này. Chúc vui vẻ :)


Rất hữu ích. Cảm ơn rất nhiều!
Christoffer Helgelin Hald

Thật tuyệt, tôi thích phiên bản demo :)
Fabrizio Bertoglio

Thật là một lời giải thích tuyệt vời. Cảm ơn bạn!
Vikram Khemlani

26

Javascript trong trình duyệt chỉ thực sự có một vài phạm vi hiệu quả: phạm vi chức năng và phạm vi toàn cầu.

Nếu một biến không nằm trong phạm vi chức năng, thì đó là trong phạm vi toàn cầu. Và các biến toàn cục nói chung là xấu, vì vậy đây là một cấu trúc để giữ các biến của thư viện cho chính nó.


1
Nhưng không phải hàm xây dựng tự cung cấp phạm vi cho các biến của chính nó?
Andrew Kou

1
Có, mỗi hàm được xác định trong thư viện này có thể xác định các biến cục bộ của riêng nó, nhưng điều này cho phép các biến được chia sẻ giữa các hàm mà không bị rò rỉ bên ngoài thư viện
Gareth

@Gareth, do đó, điều này cho phép các biến "toàn cầu" trong một phạm vi (;
Francisco Presencia

2
@FranciscoPresencia "toàn cầu trong một phạm vi" không phải là một cụm từ hữu ích, bởi vì về cơ bản đó chỉ là "phạm vi" nghĩa là gì. Điểm chung của phạm vi "toàn cầu" là nó đặc biệt là phạm vi mà tất cả các phạm vi khác có quyền truy cập.
Gareth

19

Đó gọi là đóng cửa. Về cơ bản, nó niêm phong mã bên trong hàm để các thư viện khác không can thiệp vào nó. Nó tương tự như tạo một không gian tên trong các ngôn ngữ được biên dịch.

Thí dụ. Giả sử tôi viết:

(function() {

    var x = 2;

    // do stuff with x

})();

Bây giờ các thư viện khác không thể truy cập vào biến xtôi đã tạo để sử dụng trong thư viện của mình.


7
Cẩn thận với thuật ngữ của bạn. Không gian tên ngụ ý rằng các biến có thể được truy cập từ bên ngoài bằng cách giải quyết không gian tên (thường bằng cách sử dụng tiền tố). Mặc dù điều này là có thể trong Javascript mà không phải là những gì được thể hiện ở đây
Gareth

Tôi đồng ý rằng nó không chính xác như một không gian tên, tuy nhiên, bạn có thể cung cấp chức năng tương tự bằng cách trả về một đối tượng có các thuộc tính mà bạn muốn công khai : (function(){ ... return { publicProp1: 'blah' }; })();. Rõ ràng không hoàn toàn song song với không gian tên, nhưng nó có thể giúp nghĩ về nó theo cách đó.
Joel

trong ví dụ của bạn x vẫn là một biến riêng tư ... Mặc dù bạn gói nó trong IIFE. hãy tiếp tục và cố gắng truy cập x bên ngoài chức năng, bạn không thể ..
RayLovless

Quan điểm của bạn không hợp lệ. Ngay cả trong các chức năng sau đây, các thư viện khác không thể truy cập x. function () {var x = 2}
RayLovless

@RayLovless Tôi đồng ý. Tôi không mâu thuẫn với lời khẳng định đó. Trong thực tế, tôi đã đưa ra khẳng định tương tự như câu cuối cùng của câu trả lời này.
Joel

8

Bạn cũng có thể sử dụng các hàm đóng như dữ liệu trong các biểu thức lớn hơn, như trong phương pháp xác định hỗ trợ trình duyệt này cho một số đối tượng html5.

   navigator.html5={
     canvas: (function(){
      var dc= document.createElement('canvas');
      if(!dc.getContext) return 0;
      var c= dc.getContext('2d');
      return typeof c.fillText== 'function'? 2: 1;
     })(),
     localStorage: (function(){
      return !!window.localStorage;
     })(),
     webworkers: (function(){
      return !!window.Worker;
     })(),
     offline: (function(){
      return !!window.applicationCache;
     })()
    }

Làm gì thế !! làm gì
1,21 gigawatt

!! chuyển đổi một giá trị thành biểu diễn boolean (đúng / sai).
Liam

7

Ngoài việc giữ các biến cục bộ, một cách sử dụng rất tiện dụng là khi viết thư viện bằng biến toàn cục, bạn có thể đặt cho nó một tên biến ngắn hơn để sử dụng trong thư viện. Nó thường được sử dụng trong việc viết các trình cắm jQuery, vì jQuery cho phép bạn vô hiệu hóa biến $ trỏ đến jQuery, sử dụng jQuery.noConflict (). Trong trường hợp nó bị vô hiệu hóa, mã của bạn vẫn có thể sử dụng $ và không bị hỏng nếu bạn chỉ cần làm:

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

3
  1. Để tránh xung đột với các phương thức / thư viện khác trong cùng một cửa sổ,
  2. Tránh phạm vi toàn cầu, làm cho phạm vi địa phương,
  3. Để làm cho việc gỡ lỗi nhanh hơn (phạm vi cục bộ),
  4. JavaScript chỉ có phạm vi chức năng, vì vậy nó cũng sẽ giúp biên dịch mã.

1

Chúng ta cũng nên sử dụng 'userict' trong hàm scope để đảm bảo rằng mã phải được thực thi trong "chế độ nghiêm ngặt". Mã mẫu được hiển thị dưới đây

(function() {
    'use strict';

    //Your code from here
})();

Tại sao chúng ta nên sử dụng nghiêm ngặt?
nbro

Kiểm tra bài viết này: stackoverflow.com/questions/1335851/
Ấn

Không thực sự trả lời câu hỏi!
Pritam Banerjee

Pritam, nó là một thực hành sử dụng tốt. Vui lòng thực hiện nghiên cứu thích hợp trước khi bỏ phiếu bất kỳ câu trả lời nào
Neha Jain

1
'Sử dụng nghiêm ngặt' cứu các lập trình viên xấu từ chính họ. Và vì phần lớn các lập trình viên là những lập trình viên tồi, điều đó giúp ngăn họ làm những việc họ chắc chắn không nên làm và kết thúc trong một mớ hỗn độn mã nhanh chóng.
MattE
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.