Tại sao tôi có thể sử dụng một hàm trước khi nó được xác định trong JavaScript?


167

Mã này luôn hoạt động, ngay cả trong các trình duyệt khác nhau:

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 tại sao nó nên hoạt động, 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. Không có lời giải thích ở đó hoặc bất cứ nơi nào cho vấn đề đó.

Ai đó có thể xin giác ngộ tôi?


3
Trong các phiên bản mới hơn của firefox, điều này không hoạt động nếu mã đang thử / bắt. Xem câu đố này: jsfiddle.net/qzzc1evt
Joshua Smith

Câu trả lời:


217

Các functiontuyên bố là kỳ diệu và gây ra định danh của nó bị ràng buộc trước khi bất cứ điều gì trong mã khối của nó * được thực thi.

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

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

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

nó sẽ ngừng hoạt động

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

Điều này được ghi lại trong tiêu chuẩn ECMAScript , 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!

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


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 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.
Edu Felipe

2
@bobince Được rồi, tôi bắt đầu nghi ngờ chính mình khi tôi không thể tìm thấy một đề cập nào về thuật ngữ này đang nâng cấp trên trang này. Hy vọng rằng những nhận xét này có đủ Google Juice ™ để đặt mọi thứ đúng :)
Mathias Bynens

2
Đây là một kết hợp câu hỏi / câu trả lời phổ biến. Xem xét cập nhật với một liên kết / đoạn trích đến đặc tả chú thích ES5. (Cái nào dễ truy cập hơn một chút.)

1
Bài viết này có một số ví dụ: JavaScript-Scoping-and-
Hoist

Tôi tìm thấy khá nhiều thư viện sử dụng hàm trước khi định nghĩa, thậm chí một số ngôn ngữ cho phép nó chính thức, ví dụ. Haskell. Thành thật mà nói, điều này có thể không phải là một điều xấu, vì bạn có thể viết một chút biểu cảm hơn trong một số trường hợp.
Windmaomao

27

Nó được gọi là HOISTING - Gọi (gọi) một chức năng trước khi nó được xác định.

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

Hàm biểu thức & Hàm khai báo

  1. Hàm biểu thức:

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

    Để gọi (gọi) các hàm này, chúng luôn cần một tên biến . Loại chức năng này sẽ không hoạt động nếu nó được gọi trước khi nó được xác định, điều đó có nghĩa là Tời không xảy ra ở đây. Chúng ta phải luôn xác định 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 nó trong ECMAScript 6:

    lastName = (family) => console.log("My last name is " + family);
    
    x = lastName("Lopez");
  2. Chức năng khai báo:

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

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

    Ví dụ nâng hàng:

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

let fun = theFunction; fun(); function theFunction() {}cũng sẽ hoạt động (Nút và trình duyệt)
fider 15/03/19

14

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 đoạn thực thi (khai báo biến, định nghĩa hàm, v.v.) Nhưng tại bất kỳ thời điểm nào, chỉ có thể sử dụng những gì được xác định trong tập lệnh trước thời điểm đó.

Điều này khác với các bối cảnh lập trình khác xử lý (biên dịch) tất cả mã nguồn của bạn, có thể liên kết nó với bất kỳ thư viện nào bạn cần để giải quyết các tham chiếu và xây dựng một mô-đun thực thi, tại đó bắt đầu thực thi điểm.

Mã của bạn có thể tham chiếu đến các đối tượng được đặt tên (biến, các hàm khác, v.v.) được xác định rõ hơn cùng, nhưng bạn không thể thực thi mã giới thiệu cho đến khi tất cả các phần có sẵn.

Khi bạn trở nên quen thuộc với JavaScript, bạn sẽ nhận thức sâu sắc về nhu cầu của bạn để viết mọi thứ theo trình tự thích hợp.

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


3

Một số ngôn ngữ có yêu cầu rằng định danh 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 đường chuyền duy nhất trên mã nguồn.

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


2

Tôi chỉ sử dụng JavaScript một chút. Tôi không chắc chắn nếu điều này sẽ giúp, nhưng nó trông rất giống với những gì bạn đang nói và có thể cung cấp một số cái nhìn sâu sắc:

http://www.dustindiaz.com/javascript-feft-declaration-ambiguity/


Các liên kết không còn chết.
mwclarke

1
Liên kết đã chết.
Jerome Indefenzo

Đây là một ảnh chụp nhanh từ archive.org. Có vẻ như tác giả đã gỡ bỏ toàn bộ trang web của họ vì có nội dung lỗi thời, không phải bài đăng trên blog này thuộc danh mục đó.
jkmartindale

1

Phần thân của hàm "InternalFoo" cần phải đi đâu đó vào thời gian phân tích cú pháp, vì vậy khi mã được đọc (còn gọi là phân tích cú pháp) bởi 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 đó, mã được chạy, JavaScript thực sự cố gắng tìm hiểu xem "InternalFoo" có tồn tại hay không và nó có thể được gọi hay không, v.v.


-4

Vì lý do tương tự, những điều sau đây sẽ luôn được đặt footrong không gian tên toàn cầu:

if (test condition) {
    var foo;
}

8
Trên thực tế, đó là vì những lý do rất khác nhau. Các ifkhối không tạo ra một phạm vi, trong khi một function()khối luôn tạo ra một. Lý do thực sự là định nghĩa về tên javascript toàn cầu xảy ra ở giai đoạn biên dịch, do đó ngay cả khi mã không chạy, tên được xác định. (Xin lỗi, mất quá nhiều thời gian để bình luận)
Edu Felipe
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.