Vị trí của dấu ngoặc đơn để tự động thực thi các hàm JavaScript ẩn danh?


107

Gần đây tôi đã so sánh phiên bản hiện tại của json2.js với phiên bản mà tôi có trong dự án của mình và nhận thấy sự khác biệt về cách biểu thức hàm được tạo và tự thực thi.

Mã được sử dụng để bọc một hàm ẩn danh trong ngoặc đơn và sau đó thực thi nó,

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

nhưng bây giờ nó bao bọc hàm tự động thực thi trong ngoặc đơn.

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

Có một nhận xét của CMS trong câu trả lời được chấp nhận của Giải thích cú pháp hàm ẩn danh được đóng gói của JavaScript rằng "cả hai: (function(){})();(function(){}());đều hợp lệ."

Tôi đã tự hỏi sự khác biệt là gì? Cái trước có chiếm bộ nhớ bằng cách để lại một chức năng ẩn danh toàn cầu không? Dấu ngoặc phải được đặt ở đâu?



Liên quan: Cú pháp gọi hàm ngay lập tức (trong JSLint)
Bergi

1
Đồng thời đọc về mục đích của cấu trúc này , hoặc kiểm tra giải thích ( kỹ thuật ) (cũng tại đây ). Để biết tại sao dấu ngoặc đơn lại cần thiết, hãy xem câu hỏi này .
Bergi

Câu trả lời:


66

Chúng hầu như giống nhau.

Đầu tiên bao quanh dấu ngoặc đơn xung quanh một hàm để biến nó thành một biểu thức hợp lệ và gọi nó. Kết quả của biểu thức là không xác định.

Hàm thứ hai thực thi hàm và các dấu ngoặc đơn xung quanh lệnh gọi tự động làm cho nó trở thành một biểu thức hợp lệ. Nó cũng đánh giá là không xác định.

Tôi không nghĩ rằng có một cách "đúng" để làm điều đó, vì kết quả của biểu thức là như nhau.

> function(){}()
SyntaxError: Unexpected token (
> (function(){})()
undefined
> (function(){return 'foo'})()
"foo"
> (function(){ return 'foo'}())
"foo"

8
JSLint muốn "(function () {} ());". JSLint nói, "Di chuyển lời gọi vào các parens có chứa hàm."
XP1

27
Trên thực tế, bạn không bị giới hạn ở hai điều đó, bạn có thể sử dụng bất cứ thứ gì khiến trình biên dịch nhận ra hàm là một phần của một biểu thức chứ không phải một câu lệnh, chẳng hạn như +function(){}()hoặc !function(){}().
Tgr

49
@ XP1: JSLint muốn nhiều thứ đặc trưng cho phong cách của Crockford hơn là bản chất. Đây là một trong số họ.
TJ Crowder

@TJCrowder. Bạn muốn giới thiệu điều gì? jQuery sử dụng kiểu đầu tiên và Crockford sử dụng kiểu thứ hai.
Teej

4
@ThorpeObazee: Thực sự không quan trọng, vì vậy hãy làm bất cứ điều gì bạn thích. Tôi khuyên bạn nên chống lại một số trong những người Outre hơn ( -function(){}();, !function(){}();, và về cơ bản bất kỳ nhà điều hành khác ngay trước functioncũng làm việc, nhưng tôi muốn gắn bó với các phiên bản sử dụng dấu ngoặc). Tôi thấy cái đầu tiên nhiều hơn cái thứ hai, và đó là sở thích của tôi; nó cũng có ý nghĩa hơn đối với tôi, nhưng đó là chủ quan. FWIW: jsbin.com/ejaqow
TJ Crowder

13

Trong trường hợp đó, nó không quan trọng. Bạn đang gọi một biểu thức phân giải thành một hàm trong định nghĩa đầu tiên, đồng thời xác định và gọi ngay một hàm trong ví dụ thứ hai. Chúng giống nhau vì biểu thức hàm trong ví dụ đầu tiên chỉ là định nghĩa hàm.

Có những trường hợp khác rõ ràng hữu ích hơn để gọi các biểu thức giải quyết các hàm:

(foo || bar)()

3
Để làm rõ cho những người đọc khác (chủ yếu là do bản thân tôi lúc đầu cũng không hiểu :))), foo và / hoặc bar phải bằng một số chức năng. (ví dụ foo = function(){alert('hi');}. Nếu không phải là một chức năng, một lỗi được ném.
Alexander Bird

3
@AlexanderBird Làm rõ thêm - Nó cũng sẽ xuất hiện một lỗi nếu foolà "sự thật" nhưng không phải là một hàm.
JLRishe

9

Không có bất kỳ sự khác biệt nào ngoài cú pháp.

Về mối quan tâm của bạn về phương pháp thực hiện thứ hai:

Xem xét:

(function namedfunc () { ... }())

namedfuncsẽ vẫn không ở phạm vi toàn cầu mặc dù bạn đã cung cấp tên. Tương tự với các chức năng ẩn danh. Cách duy nhất để có được nó trong phạm vi đó là gán nó cho một biến bên trong parens.

((namedfunc = function namedfunc () { ... })())

Các parens bên ngoài là không cần thiết:

(namedfunc = function namedfunc () { ... })()

Nhưng bạn không muốn tuyên bố toàn cầu đó, phải không?

Vì vậy, nó tổng hợp thành:

(function namedfunc () { ... })()

Và bạn có thể giảm nó hơn nữa: tên là không cần thiết vì nó sẽ không bao giờ được sử dụng (trừ khi hàm của bạn là đệ quy .. và thậm chí sau đó bạn có thể sử dụng arguments.callee)

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

Đó là cách tôi nghĩ về nó (có thể không chính xác, tôi chưa đọc thông số kỹ thuật của ECMAScript). Hy vọng nó giúp.


Lưu ý rằng arguments.calleekhông được dùng nữa kể từ ES5 (và bị cấm ở chế độ nghiêm ngặt).
nyuszika7h

"Các parens bên ngoài là không cần thiết:" - Tôi nghĩ rằng chúng ngăn ngừa lỗi khi các tệp được nối với nhau, nếu không bạn cần một! hoặc một cái gì đó.
Jimmyt1988,

-3

Sự khác biệt chỉ tồn tại bởi vì Douglas Crockford không thích kiểu đầu tiên cho IIFEs ! (seriuosly) Như bạn có thể thấy trong video này !! .

Lý do duy nhất cho sự tồn tại của gói phụ () {trong cả hai kiểu} là để giúp tạo phần mã đó Biểu thức hàm , vì Khai báo hàm không thể được gọi ngay lập tức. Một số kịch bản / Rút gọn-ERS chỉ sử dụng +, !, -& ~thay vì quá ngoặc đơn. Như thế này:

+function() {  
    var foo = 'bar';  
}();

!function() {  
    var foo = 'bar';  
}();

-function() {  
    var foo = 'bar';  
}();

~function() {  
    var foo = 'bar';  
}();

Và tất cả những điều này hoàn toàn giống với các lựa chọn thay thế của bạn. Lựa chọn trong số các trường hợp này là hoàn toàn của riêng bạn và không có sự khác biệt. {Những người với ()sản phẩm 1 Byte tập tin lớn hơn ;-)}

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.