Mẫu JavaScript này được gọi là gì và tại sao nó được sử dụng?


100

Tôi đang nghiên cứu THREE.js và nhận thấy một mẫu trong đó các hàm được định nghĩa như vậy:

var foo = ( function () {
    var bar = new Bar();

    return function ( ) {
        //actual logic using bar from above.
        //return result;
    };
}());

(Ví dụ xem phương pháp raycast tại đây ).

Các bình thường biến thể của một phương pháp như vậy sẽ giống như thế này:

var foo = function () {
    var bar = new Bar();

    //actual logic.
    //return result;
};

So sánh phiên bản đầu tiên với biến thể bình thường , phiên bản đầu tiên dường như khác ở điểm:

  1. Nó chỉ định kết quả của một chức năng tự thực thi.
  2. Nó định nghĩa một biến cục bộ trong hàm này.
  3. Nó trả về hàm thực có chứa logic sử dụng biến cục bộ.

Vì vậy, sự khác biệt chính là trong biến thể đầu tiên, thanh chỉ được gán một lần khi khởi tạo, trong khi biến thể thứ hai tạo biến tạm thời này mỗi khi nó được gọi.

Dự đoán tốt nhất của tôi về lý do tại sao điều này được sử dụng là nó giới hạn số lượng phiên bản cho thanh (sẽ chỉ có một) và do đó tiết kiệm chi phí quản lý bộ nhớ.

Những câu hỏi của tôi:

  1. Giả thiết này có đúng không?
  2. Có tên cho mẫu này không?
  3. Tại sao cái này được sử dụng?

1
@ChrisHayes đủ công bằng. Tôi đã gắn thẻ nó là THREE.js vì tôi nghĩ rằng BA cộng tác viên là những người đủ điều kiện nhất để trả lời câu hỏi này nhưng có, đó là một câu hỏi JS chung chung.
Patrick Klug,

2
Tôi tin rằng nó được gọi là đóng cửa. Bạn có thể đọc về chúng.
StackFlowed

1
Nếu đây là nơi duy nhất một Bar được khởi tạo, thì đó là một mẫu singleton .
Paul

8
Không nhất thiết để tiết kiệm bộ nhớ, nhưng nó có thể giữ trạng thái qua lời gọi
Juan Mendes

2
@wrongAnswer: không chính xác. ở đây hàm ẩn danh (sẽ là hàm đóng) được thực thi ngay lập tức.
njzk2

Câu trả lời:


100

Giả định của bạn gần như đúng. Chúng ta hãy xem xét những người đầu tiên.

  1. Nó chỉ định trả về của một chức năng tự thực thi

Đây được gọi là biểu thức hàm được gọi ngay lập tức hoặc IIFE

  1. Nó định nghĩa một biến cục bộ trong hàm này

Đây là cách để có các trường đối tượng riêng trong JavaScript vì nó không cung cấp privatetừ khóa hoặc chức năng khác.

  1. Nó trả về hàm thực có chứa logic sử dụng biến cục bộ.

Một lần nữa, điểm chính là biến cục bộ này là riêng tư .

Có tên cho mẫu này không?

AFAIK bạn có thể gọi mẫu này là Mẫu mô -đun . Trích dẫn:

Mẫu Mô-đun đóng gói "quyền riêng tư", trạng thái và tổ chức bằng cách sử dụng các đóng. Nó cung cấp một cách kết hợp các phương thức và biến công khai và riêng tư, bảo vệ các phần không bị rò rỉ vào phạm vi toàn cầu và vô tình va chạm với giao diện của nhà phát triển khác. Với mẫu này, chỉ một API công khai được trả về, giữ mọi thứ khác trong vùng đóng ở chế độ riêng tư.

So sánh hai ví dụ đó, phỏng đoán tốt nhất của tôi về lý do tại sao cái đầu tiên được sử dụng là:

  1. Nó đang thực hiện mô hình thiết kế Singleton.
  2. Người ta có thể kiểm soát cách một đối tượng của một kiểu cụ thể có thể được tạo bằng cách sử dụng ví dụ đầu tiên. Một điểm phù hợp nhất với điểm này có thể là các phương thức nhà máy tĩnh như được mô tả trong Java hiệu quả.
  3. Sẽ rất hiệu quả nếu bạn luôn cần cùng một trạng thái đối tượng.

Nhưng nếu bạn chỉ cần đối tượng vani mỗi lần, thì họa tiết này có thể sẽ không thêm bất kỳ giá trị nào.


1
+1 để xác định chính xác mẫu từ sách của Addy Osmani. Bạn đã đặt tên chính xác - đây thực sự là mô-đun - nhân tiện, mô-đun tiết lộ về điều đó.
Benjamin Gruenbaum,

4
Tôi đồng ý với câu trả lời của bạn, ngoại trừ phần 'không có biến số riêng'. Tất cả các biến JS đều có phạm vi từ vựng là 'out-of-the-box', đây là một cơ chế mạnh mẽ / chung hơn so với các biến "private" (ví dụ: như được tìm thấy trong Java); do đó JS hỗ trợ các biến "private" như một trường hợp đặc biệt của cách nó xử lý tất cả các biến.
Warbo

Tôi nghĩ rằng bằng cách "biến tư nhân", bạn có nghĩa là "lĩnh vực đối tượng" private
Ian Ringrose


4
@LukaHorvat: thực tế là, javascript không "mạnh" hơn các ngôn ngữ khác (tôi thích thuật ngữ biểu cảm hơn). Trên thực tế, nó ít diễn đạt hơn, vì cách duy nhất để bảo vệ các biến của bạn là bao bọc chúng trong chức năng để tránh mọi điều vô nghĩa tái sử dụng biến. Mô-đun mô-đun là một điều kiện cần thiết để tạo ra mã javascript tốt, nhưng nó không phải là một tính năng của ngôn ngữ, đó là một giải pháp đáng buồn hơn để tránh bị những điểm yếu của ngôn ngữ đó cắn.
Falanwe

11

Nó giới hạn chi phí khởi tạo đối tượng và cũng đảm bảo rằng tất cả các lệnh gọi hàm đều sử dụng cùng một đối tượng. Điều này cho phép, ví dụ, trạng thái được lưu trữ trong đối tượng để sử dụng các lệnh gọi trong tương lai.

Mặc dù có thể nó giới hạn việc sử dụng bộ nhớ, nhưng thường thì GC sẽ thu thập các đối tượng không sử dụng, vì vậy mô hình này không có khả năng giúp ích nhiều.

Mô hình này là một hình thức đóng cửa cụ thể .


1
Trong JS nó thường reffers như 'module'
Lesha Ogonkov

2
Tôi sẽ không gọi nó là "một hình thức đóng cửa cụ thể". Đó là một mẫu sử dụng một dấu đóng. Tên của mô hình vẫn còn để lấy.
Chris Hayes

4
Nó có thực sự cần một cái tên? Mọi thứ có phải theo khuôn mẫu không? Chúng ta có thực sự cần phân loại vô tận của các biến thể "mô-đun" không? Nó không thể chỉ là "một IIFE với một số biến cục bộ trả về một hàm?"
Dagg Nabbit

3
@DaggNabbit Khi câu hỏi là "mô hình này được gọi là gì"? Đúng, nó cần một cái tên hoặc nếu không thì một lý lẽ thuyết phục mà nó không có. Bên cạnh đó, các mẫu tồn tại là có lý do. Tôi không hiểu tại sao bạn lại chống lại họ ở đây.
Chris Hayes

4
@ChrisHayes nếu nó cần một cái tên, tại sao bạn phải đưa ra đối số rằng nó không có? Điều đó không có ý nghĩa gì. Nếu nó cần một, nó phải không có một. Tôi không có vấn đề với các mẫu, nhưng tôi không nghĩ rằng cần phải phân loại mọi thành ngữ đơn giản như một mẫu. Làm điều đó dẫn đến suy nghĩ theo những cách hạn chế ("Đây có phải là một mẫu mô-đun không? Tôi có đang sử dụng đúng mẫu mô-đun không?" So với "Tôi có IIFE với một số biến cục bộ trả về một hàm, thiết kế này có phù hợp với tôi không?")
Dagg Nabbit

8

Tôi không chắc liệu mẫu này có tên chính xác hơn không, nhưng đối với tôi thì đây giống như một mô-đun và lý do nó được sử dụng là để đóng gói và duy trì trạng thái.

Việc đóng (được xác định bởi một hàm bên trong một hàm) đảm bảo rằng hàm bên trong có quyền truy cập vào các biến bên trong hàm bên ngoài.

Trong ví dụ bạn đã đưa ra, hàm bên trong được trả về (và được gán cho foo) bằng cách thực thi hàm bên ngoài, có nghĩa là tmpObjecttiếp tục tồn tại bên trong bao đóng và nhiều lệnh gọi đến hàm bên trong foo()sẽ hoạt động trên cùng một phiên bản của tmpObject.


5

Sự khác biệt chính giữa mã của bạn và mã Three.js là trong mã Three.js, biến tmpObjectchỉ được khởi tạo một lần và sau đó được chia sẻ bởi mọi lệnh gọi của hàm được trả về.

Điều này sẽ hữu ích để giữ một số trạng thái giữa các lần gọi, tương tự như cách staticcác biến được sử dụng trong các ngôn ngữ giống C.

tmpObject là một biến private chỉ hiển thị cho hàm bên trong.

Nó thay đổi cách sử dụng bộ nhớ, nhưng nó không được thiết kế để tiết kiệm bộ nhớ.


5

Tôi muốn đóng góp vào chủ đề thú vị này bằng cách mở rộng khái niệm về mô-đun tiết lộ, đảm bảo rằng tất cả các phương thức và biến được giữ kín cho đến khi chúng được hiển thị rõ ràng.

nhập mô tả hình ảnh ở đây

Trong trường hợp thứ hai, phương thức cộng sẽ được gọi là Calculator.add ();


0

Trong ví dụ được cung cấp, đoạn mã đầu tiên sẽ sử dụng cùng một phiên bản của tmpObject cho mọi lệnh gọi hàm foo (), trong đó, như trong đoạn mã thứ hai, tmpObject sẽ là một phiên bản mới mỗi lần.

Một lý do khiến đoạn mã đầu tiên có thể được sử dụng, đó là biến tmpObject có thể được chia sẻ giữa các lệnh gọi tới foo (), mà giá trị của nó không bị rò rỉ vào phạm vi mà foo () được khai báo.

Phiên bản hàm không được thực thi ngay lập tức của đoạn mã đầu tiên sẽ thực sự trông như thế này:

var tmpObject = new Bar();

function foo(){
    // Use tmpObject.
}

Tuy nhiên, lưu ý rằng phiên bản này có tmpObject trong cùng phạm vi với foo (), vì vậy nó có thể được thao tác sau này.

Một cách tốt hơn để đạt được cùng một chức năng sẽ là sử dụng một mô-đun riêng biệt:

Mô-đun 'foo.js':

var tmpObject = new Bar();

module.exports = function foo(){
    // Use tmpObject.
};

Mô-đun 2:

var foo = require('./foo');

So sánh giữa hiệu suất của IEF và chức năng tạo foo được đặt tên: http://jsperf.com/ief-vs-name-osystem


3
Ví dụ 'tốt hơn' của bạn chỉ hoạt động trong NodeJS và bạn chưa giải thích cách nó tốt hơn.
Benjamin Gruenbaum,

Tạo một mô-đun riêng biệt không phải là "tốt hơn", nó chỉ khác. Đặc biệt, đó là một cách để thu gọn các chức năng bậc cao xuống các đối tượng bậc nhất. Mã bậc một có xu hướng dễ đọc hơn, nhưng nhìn chung dài dòng hơn và buộc chúng ta phải sửa đổi các kết quả trung gian.
Warbo

@BenjaminGruenbaum Mô-đun không chỉ có trong Node, có rất nhiều giải pháp mô-đun phía máy khách, ví dụ: Browserify. Tôi coi giải pháp mô-đun là "tốt hơn" vì nó dễ đọc hơn, dễ gỡ lỗi hơn và rõ ràng hơn về những gì trong phạm vi và ở đâu.
Kory Nunn
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.