5

Tôi đang xem section 13 hoặc đặc tả ECMAScript (câu 5). Biểu thức hàm ẩn danh được khởi tạo như sau:Tại sao các biểu thức hàm ẩn danh và các biểu thức hàm được đặt tên được khởi tạo khác nhau?

Trả về kết quả của việc tạo đối tượng hàm mới như được chỉ định trong 13.2 với các tham số được chỉ định bởi FunctionBodyListopt và nội dung được chỉ định bởi FunctionBody. Vượt qua trong môi trường LexicalEnvironment của bối cảnh thực thi đang chạy dưới dạng Phạm vi. Chuyển đúng như cờ Nghiêm ngặt nếu FunctionExpression được chứa trong mã nghiêm ngặt hoặc nếu FunctionBody của nó là mã nghiêm ngặt.

logic này là rất tương tự như cách khai báo hàm được khởi tạo. Tuy nhiên, lưu ý cách khởi tạo khác nhau của một biểu thức funciton được đặt tên là.

  1. Hãy funcEnv thể là kết quả của việc gọi NewDeclarativeEnvironment đi từ vựng Môi trường chạy thực hiện bối cảnh của như là đối số
  2. Hãy envRec được kỷ lục môi trường funcEnv của.
  3. Gọi phương thức cụ thể CreateImmutableBinding của envRec chuyển giá trị Chuỗi của Định danh làm đối số.
  4. Để đóng cửa là kết quả của việc tạo đối tượng hàm mới như được chỉ định trong 13.2 với các tham số được chỉ định bởi FormalParameterListopt và phần thân được chỉ định bởi FunctionBody. Vượt qua funcEnv làm Phạm vi. Vượt qua đúng như cờ Nghiêm ngặt nếu FunctionExpression được chứa trong mã số nghiêm ngặt hoặc nếu FunctionBody của nó là mã nghiêm ngặt.
  5. Gọi phương thức bê tông InitializeImmutableBinding của envRec chuyển giá trị Chuỗi của Mã định danh và đóng thành các đối số.
  6. Đóng cửa trở lại.

Tôi biết một trong những khác biệt lớn giữa tên/biểu thức chức năng ẩn danh được rằng biểu thức hàm có tên có thể được gọi là đệ quy từ bên trong chức năng, nhưng đó là tất cả tôi có thể nghĩ đến. Tại sao quá trình thiết lập lại khác biệt và tại sao cần thực hiện các bước bổ sung đó?

Trả lời

9

Lý do cho tất cả "nhảy" rất đơn giản.

Mã định danh của biểu thức hàm được đặt tên cần phải có sẵn trong phạm vi chức năng nhưng không nằm ngoài phạm vi.

typeof f; // undefined 

(function f() { 
    typeof f; // function 
})(); 

Làm cách nào để bạn thực hiện f có sẵn trong chức năng?

Bạn không thể tạo liên kết trong Môi trường Lexical bên ngoài vì f không khả dụng ở bên ngoài. Và bạn không thể tạo ràng buộc trong môi trường biến bên trong vì ... nó chưa được tạo ra; chức năng vẫn chưa được thực hiện tại thời điểm diễn ra, và vì vậy bước 10.4.3 (Nhập mã chức năng) với NewDeclarativeEnvironment của nó chưa bao giờ xảy ra. Vì vậy, cách thức này được thực hiện bằng cách tạo ra một môi trường từ vựng trung gian trung gian mà "kế thừa" trực tiếp từ hiện tại và sau đó được chuyển thành [[Phạm vi]] vào hàm mới được tạo.

Bạn có thể thấy rõ điều này nếu chúng ta phá vỡ bước trong 13 thành mã giả:

// create new binding layer 
funcEnv = NewDeclarativeEnvironment(current Lexical Environment) 

envRec = funcEnv 
// give it function's identifier 
envRec.CreateImmutableBinding(Identifier) 

// create function with this intermediate binding layer 
closure = CreateNewFunction(funcEnv) 

// assign newly created function to an identifier within this intermediate binding layer 
envRec.InitializeImmutableBinding(Identifier, closure) 

Vì vậy, môi trường từ vựng trong f (khi giải quyết định, ví dụ) bây giờ trông như thế này:

(function f(){ 

    [global environment] <- [f: function(){}] <- [Current Variable Environment] 

})(); 

Với chức năng ẩn danh, nó sẽ trông giống như sau:

(function() { 

    [global environment] <- [Current Variable Environment] 

})(); 
+2

Có những sự tinh tế khác. Ràng buộc tên biểu thức hàm là chỉ đọc nhưng bạn vẫn được phép khai báo một var hoặc hàm sử dụng cùng tên trong phần thân của biểu thức hàm. Mô tả ngữ nghĩa này (hãy nhớ rằng đây chỉ là một đặc tả.) Yêu cầu sử dụng một bản ghi môi trường bổ sung. –

+0

Thú vị. Nhưng tại sao điều này đòi hỏi hồ sơ môi trường bổ sung? Ví dụ, nếu ràng buộc định danh của NFE được tạo ra trong quá trình khai báo hàm (10.5) trước bước 5, bất kỳ khai báo var/function nào trong nguồn sẽ chỉ ghi đè ràng buộc của NFE (5f) thay vì đổ bóng nó. Thực tế cùng một hiệu quả, không? – kangax

1

Sự khác biệt cốt lõi giữa hai giao dịch với phạm vi (mặc dù, nếu không có gì khác, bạn tò mò muốn biết số tiền thực sự liên quan đến việc làm như vậy;) - và bạn đã chính xác trong việc chỉ ra sự khác biệt chính giữa tên/biểu thức chức năng ẩn danh là cách dễ dàng gọi những người được đặt tên theo cách đệ quy.

Tại sao tôi nói dễ dàng? Vâng, không có gì thực sự ngăn cản bạn từ calling an anonymous function recursively nhưng nó chỉ đơn giản không đẹp:

//silly factorial, 5! 
(function(n) { 
    if (n<=1) return 1; 
    return (n*arguments.callee(n-1)); //arguments.callee is so 1990s! 
})(5); 

Trong thực tế, đó là exactly what MDN says in describing named function expressions!

Nếu bạn muốn tham chiếu đến hàm hiện tại bên trong thân hàm, bạn cần tạo biểu thức hàm có tên. Tên này sau đó chỉ là chỉ địa phương cho thân hàm (phạm vi). Điều này cũng tránh sử dụng thuộc tính arguments2allee không chuẩn.

+1

Và 'arguments.calle e' không được phép ở chế độ nghiêm ngặt. – jfriend00

+0

@ jfriend00: ... và tôi không nói nó là :) ngay cả * không * ở chế độ nghiêm ngặt, nó không được chấp nhận/cau mày. Đây là lý do chính xác tại sao các biểu thức hàm được đặt tên là một thứ - để cho phép đệ quy - nghĩa là định danh hàm như được đặt tên chỉ là cục bộ cho phạm vi chức năng! –

+0

Tôi chỉ thêm một phần thông tin bổ sung làm lý do khác không sử dụng 'arguments.callee'. Không cần phải phòng thủ. – jfriend00

Các vấn đề liên quan