Thứ tự hàm JavaScript: tại sao nó quan trọng?


106

Câu hỏi gốc:

JSHint phàn nàn khi JavaScript của tôi gọi một hàm được định nghĩa sâu xuống trang hơn là lệnh gọi hàm đó. Tuy nhiên, trang của tôi là dành cho một trò chơi và không có chức năng nào được gọi cho đến khi toàn bộ nội dung được tải xuống. Vậy tại sao các hàm đặt hàng lại xuất hiện trong mã của tôi?

CHỈNH SỬA: Tôi nghĩ rằng tôi có thể đã tìm thấy câu trả lời.

http://www.adequatelygood.com/2010/2/JavaScript-Scoping-and-Hoisting

Tôi đang rên rỉ bên trong. Có vẻ như tôi cần phải dành một ngày KHÁC để đặt hàng lại sáu nghìn dòng mã. Đường cong học tập với javascript không hề dốc chút nào, nhưng nó rất loooooong.


+1 cho tài liệu tham khảo tuyệt vời trong bản cập nhật. Và tôi hy vọng điều đó thuyết phục bạn rằng bạn không thực sự cần đặt hàng lại mã của mình. :)
awm

Câu trả lời:


294

tl; dr Nếu bạn không gọi bất cứ điều gì cho đến khi mọi thứ tải, bạn sẽ ổn.


Chỉnh sửa: Để có một cái nhìn tổng quan cũng bao gồm một số khai báo ES6 ( let, const): https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Scope_Cheatsheet

Hành vi kỳ lạ này phụ thuộc vào

  1. Cách bạn xác định các chức năng và
  2. Khi bạn gọi cho họ.

Đây là một số ví dụ.

bar(); //This won't throw an error
function bar() {}

foo(); //This will throw an error
var foo = function() {}
bar();
function bar() {
    foo(); //This will throw an error
}
var foo = function() {}
bar();
function bar() {
    foo(); //This _won't_ throw an error
}
function foo() {}
function bar() {
    foo(); //no error
}
var foo = function() {}
bar();

Điều này là do một thứ gọi là cẩu !

Có hai cách để định nghĩa hàm: Khai báo hàm và biểu thức hàm . Sự khác biệt là khó chịu và phút, vì vậy chúng ta hãy nói điều này hơi sai: Nếu bạn đang viết nó giống như function name() {}, đó là một khai báo và khi bạn viết nó như var name = function() {}(hoặc một hàm ẩn danh được gán cho một kết quả trả về, những thứ tương tự), nó một biểu thức hàm .

Trước tiên, hãy xem cách các biến được xử lý:

var foo = 42;

//the interpreter turns it into this:
var foo;
foo = 42;

Bây giờ, cách khai báo hàm được xử lý:

var foo = 42;
function bar() {}

//turns into
var foo; //Insanity! It's now at the top
function bar() {}
foo = 42;

Các varbáo cáo "ném" các sáng tạo của foođể phía trên đỉnh, nhưng không gán giá trị cho nó được nêu ra. Khai báo hàm đứng ở dòng tiếp theo và cuối cùng một giá trị được gán cho foo.

Và điều này thì sao?

bar();
var foo = 42;
function bar() {}
//=>
var foo;
function bar() {}
bar();
foo = 42;

Chỉ khai báo của foođược chuyển lên trên cùng. Việc chỉ định chỉ barđược thực hiện sau khi lệnh gọi đến được thực hiện, nơi mà nó đã ở trước khi tất cả quá trình cẩu xảy ra.

Và cuối cùng, để ngắn gọn:

bar();
function bar() {}
//turns to
function bar() {}
bar();

Bây giờ, những gì về biểu thức hàm ?

var foo = function() {}
foo();
//=>
var foo;
foo = function() {}
foo();

Cũng giống như các biến thông thường, đầu tiên foođược khai báo ở điểm cao nhất của phạm vi, sau đó nó được gán một giá trị.

Hãy xem tại sao ví dụ thứ hai lại gặp lỗi.

bar();
function bar() {
    foo();
}
var foo = function() {}
//=>
var foo;
function bar() {
    foo();
}
bar();
foo = function() {}

Như chúng ta đã thấy trước đây, chỉ có quá trình tạo của foođược lưu trữ, việc chuyển nhượng đến nơi mà nó xuất hiện trong mã "gốc" (không được nâng cấp). Khi nào barđược gọi, trước đó nó foođược gán một giá trị, vì vậy foo === undefined. Bây giờ trong phần thân hàm của bar, nó như thể bạn đang làm undefined(), điều này tạo ra một lỗi.


Rất tiếc khi tìm hiểu điều này, nhưng có quá tải như Array.prototype.someMethod = function () {} được lưu trữ không? Tôi dường như gặp lỗi nếu những thứ này nằm ở cuối kịch bản của tôi.
Cạnh

Làm thế nào để bạn đảm bảo mọi thứ đều tải ? Có thông lệ nào không?
zwcloud

6

Lý do chính có lẽ là JSLint chỉ thực hiện một lần truyền vào tệp nên nó không biết bạn sẽ định nghĩa một hàm như vậy.

Nếu bạn sử dụng cú pháp câu lệnh hàm

function foo(){ ... }

Thực ra không có sự khác biệt nào ở chỗ bạn khai báo hàm (nó luôn hoạt động như thể khai báo ở đầu).

Mặt khác, nếu hàm của bạn được đặt giống như một biến thông thường

var foo = function() { ... };

Bạn phải đảm bảo rằng bạn sẽ không gọi nó trước khi khởi tạo (đây thực sự có thể là một nguồn lỗi).


Vì việc sắp xếp lại hàng tấn mã rất phức tạp và bản thân nó có thể là nguồn phát sinh lỗi, tôi khuyên bạn nên tìm kiếm một giải pháp thay thế. Tôi khá chắc rằng bạn có thể nói trước cho JSLint biết tên của các biến toàn cục để nó không phàn nàn về những thứ không được khai báo.

Đặt một nhận xét về sự bắt đầu của tệp

/*globals foo1 foo2 foo3*/

Hoặc bạn có thể sử dụng một hộp văn bản ở đó cho điều đó. (Tôi cũng nghĩ rằng bạn có thể chuyển điều này trong các đối số đến hàm jslint bên trong nếu bạn có thể can thiệp vào nó.)


Cảm ơn. Vậy dòng / * global * / sẽ hoạt động? Tốt - bất cứ điều gì để khiến JsHint thích tôi. Tôi vẫn chưa quen với JavaScript và bị tạm dừng không thể giải thích được khi tôi làm mới một trang, nhưng không có lỗi nào được báo cáo. Vì vậy, tôi nghĩ rằng giải pháp là chơi theo tất cả các quy tắc và sau đó xem nó có còn xảy ra hay không.
Chris Tolworthy

4

Có quá nhiều người thúc đẩy các quy tắc tùy ý về cách JavaScript nên được viết. Hầu hết các quy tắc là hoàn toàn rác rưởi.

Chức năng lưu trữ là một tính năng trong JavaScript vì nó là một ý tưởng hay.

Khi bạn có một hàm bên trong thường là tiện ích của các hàm bên trong, việc thêm nó vào đầu hàm bên ngoài là một cách viết mã có thể chấp nhận được, nhưng nó có nhược điểm là bạn phải đọc qua chi tiết để biết được điều gì. chức năng bên ngoài không.

Bạn nên tuân theo một nguyên tắc xuyên suốt cơ sở mã của mình hoặc đặt các chức năng riêng tư đầu tiên hoặc cuối cùng trong mô-đun hoặc chức năng của bạn. JSHint tốt để thực thi tính nhất quán, nhưng bạn nên TUYỆT ĐỐI điều chỉnh .jshintrc để phù hợp với nhu cầu của bạn, KHÔNG điều chỉnh mã nguồn của bạn theo các khái niệm mã hóa lập dị của người khác.

Một phong cách mã hóa mà bạn có thể thấy trong tự nhiên mà bạn nên tránh vì nó không mang lại lợi ích gì cho bạn và chỉ có thể gặp phải khó khăn khi cấu trúc lại:

function bigProcess() {
    var step1,step2;
    step1();
    step2();

    step1 = function() {...};
    step2 = function() {...};
}

Đây chính xác là những gì chức năng cần tránh. Chỉ cần học ngôn ngữ và khai thác thế mạnh của nó.


2

Chỉ khai báo hàm mới được lưu trữ chứ không phải biểu thức hàm (gá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.