2008-11-04 45 views
135

Mã này luôn luôn làm việc, thậm chí trong các trình duyệt khác nhau:Tại sao tôi có thể sử dụng hàm trước khi được xác định trong JavaScript?

function fooCheck() { 
    alert(internalFoo()); // We are using internalFoo() here... 

    return internalFoo(); // And here, even though it has not been defined... 

    function internalFoo() { return true; } //...until here! 
} 

fooCheck(); 

tôi không thể tìm thấy một tài liệu tham khảo duy nhất để lý do tại sao nó phải làm việc, mặc dù. Lần đầu tiên tôi thấy điều này trong ghi chú trình bày của John Resig, nhưng nó chỉ được đề cập đến. Không có lời giải thích nào hay ở đâu cho vấn đề đó.

Ai đó có thể làm sáng tỏ tôi không?

+2

Trong các phiên bản mới hơn của firefox, điều này không có tác dụng nếu các mã trong một try/catch. Xem fiddle này: http://jsfiddle.net/qzzc1evt/ –

Trả lời

182

Tuyên bố function là phép thuật và khiến mã định danh của nó bị ràng buộc trước bất kỳ thứ gì trong khối mã * được thực thi.

Điều này khác với một bài tập có biểu thức function, được đánh giá theo thứ tự từ trên xuống bình thường.

Nếu bạn đã thay đổi ví dụ để nói:

var internalFoo = function() { return true; }; 

nó sẽ ngừng làm việc.

Khai báo hàm là hoàn toàn tách biệt với biểu thức hàm, mặc dù chúng trông gần như giống hệt nhau và có thể không rõ ràng trong một số trường hợp.

Điều này được ghi lại trong ECMAScript standard, phần 10.1.3. Thật không may ECMA-262 không phải là một tài liệu rất dễ đọc ngay cả theo tiêu chuẩn tiêu chuẩn!

*: hàm chứa, khối, mô-đun hoặc tập lệnh.

+0

Tôi đoán nó thực sự không thể đọc được. Tôi chỉ đọc phần bạn đã chỉ 10.1.3 và không hiểu lý do tại sao các điều khoản sẽ gây ra hành vi này. Cảm ơn vì thông tin. –

+24

Điều này được gọi là "cẩu", phải không? –

+0

@Mathias: đúng. – bobince

2

Một số ngôn ngữ có yêu cầu rằng mã nhận dạng phải được xác định trước khi sử dụng. Một lý do cho điều này là trình biên dịch sử dụng một pass đơn trên sourcecode.

Nhưng nếu có nhiều vé (hoặc một số séc được hoãn lại), bạn hoàn toàn có thể sống mà không có yêu cầu đó. Trong trường hợp này, mã có thể được đọc đầu tiên (và diễn giải) và sau đó các liên kết được thiết lập.

0

Nội dung của hàm "internalFoo" cần phải đi đâu đó tại thời gian phân tích cú pháp, do đó khi mã được đọc (a.k.a phân tích cú pháp) của trình thông dịch JS, cấu trúc dữ liệu cho hàm được tạo và tên được gán.

Chỉ sau, sau đó mã được chạy, JavaScript thực sự cố gắng để tìm hiểu xem "internalFoo" tồn tại và nó là gì và liệu nó có thể được gọi vv

13

Trình duyệt đọc HTML của bạn từ đầu đến cuối và có thể thực thi nó khi nó được đọc và phân tích thành các khối thực thi (các khai báo biến, các định nghĩa hàm, vv) Nhưng tại bất kỳ thời điểm nào cũng chỉ có thể sử dụng những gì đã được định nghĩa trong html trước thời điểm đó. Điều này khác với các ngữ cảnh lập trình khác mà biên dịch tất cả mã nguồn của bạn, liên kết nó với bất kỳ thư viện nào bạn cần, và xây dựng một mô-đun thực thi, tại đó bắt đầu thực hiện điểm.

Bạn có thể xác định các hàm tham chiếu đến các mục (biến, hàm khác, v.v.) được xác định thêm, nhưng bạn không thể thực thi các hàm đó cho đến khi tất cả các phần có sẵn.

Khi bạn làm quen với JavaScript, bạn sẽ nhận thức sâu sắc về nhu cầu viết những thứ theo đúng trình tự.

Sửa đổi: Để xác nhận câu trả lời được chấp nhận (ở trên), hãy sử dụng Firebug để bước qua phần tập lệnh của trang web. Bạn sẽ thấy nó bỏ qua từ hàm này sang hàm khác, chỉ truy cập vào dòng đầu tiên, trước khi nó thực thi bất kỳ mã nào.

+4

+1 để sửa đổi vào cuối câu trả lời của bạn, đó là phần quan trọng. – hippietrail

-4

Đối với lý do tương tự sau đây sẽ luôn đặt foo trong không gian tên toàn cầu:

if (test condition) { 
    var foo; 
} 
+8

Thực ra, đó là vì những lý do rất khác nhau. Khối 'if' không tạo ra phạm vi, trong khi khối' function() 'luôn tạo ra một. Lý do thực sự là định nghĩa của các tên javascript toàn cầu xảy ra ở giai đoạn biên dịch, để ngay cả khi mã không chạy, tên được định nghĩa. (Xin lỗi đã quá lâu để bình luận) –

4

Nó được gọi là cẩu - Gọi (gọi là) một hàm trước nơi mà nó đã được xác định.

Hai loại khác nhau của các chức năng mà tôi muốn viết về là:

Biểu Chức năng & giảm tốc Chức năng

  1. Chức năng Biểu hiện:

    Một biểu hiện chức năng có thể được lưu trữ trong một biến nên họ không cần tên hàm. Chúng cũng sẽ được đặt tên như một hàm ẩn danh (một hàm không có tên).

    Để gọi (được gọi), họ luôn cần sử dụng tên biến số . Những loại chức năng này sẽ không hoạt động nếu các cuộc gọi trước khi nó được xác định có nghĩa là Hoisting không xảy ra ở đây. Chúng ta luôn phải định nghĩa hàm biểu thức trước và sau đó gọi nó.

    let lastName = function (family) { 
    console.log("My last name is " + family); 
    }; 
    let x = lastName("Lopez"); 
    

    Đây là cách bạn có thể viết trong ECMAScript   6:

    lastName = (family) => console.log("My last name is " + family); 
    
    x = lastName("Lopez"); 
    
  2. giảm tốc Chức năng:

    Các hàm được khai báo với cú pháp sau đây không thực hiện ngay lập tức. Chúng được "lưu lại để sử dụng sau này" và sẽ được thực thi sau này, khi chúng được gọi (được gọi). Các loại chức năng này hoạt động nếu bạn gọi chúng TRƯỚC KHI hoặc SAU nơi chúng đã được xác định. Nếu bạn gọi một hàm giảm tốc trước khi nó được xác định - Hoisting - hoạt động đúng.

    function Name(name) { 
        console.log("My cat's name is " + name); 
    } 
    Name("Chloe"); 
    

    Treo dụ:

    Name("Chloe"); 
    function Name(name) { 
        console.log("My cat's name is " + name); 
    } 
    
Các vấn đề liên quan