Dưới đây là danh sách các biểu mẫu tiêu chuẩn tạo ra các hàm: (Ban đầu được viết cho một câu hỏi khác, nhưng được điều chỉnh sau khi được chuyển sang câu hỏi chính tắc.)
Điều kiện:
Danh sách nhanh:
Tuyên bố chức năng
function
Biểu thức "ẩn danh" (bất chấp thuật ngữ, đôi khi tạo hàm với tên)
function
Biểu hiện được đặt tên
Trình khởi tạo chức năng Accessor (ES5 +)
Biểu thức hàm mũi tên (ES2015 +) (giống như các biểu thức hàm ẩn danh, không liên quan đến một tên rõ ràng và vẫn có thể tạo các hàm có tên)
Khai báo phương thức trong Trình khởi tạo đối tượng (ES2015 +)
Trình xây dựng và khai báo phương thức trong class
(ES2015 +)
Tuyên bố chức năng
Biểu mẫu đầu tiên là một khai báo hàm , trông như thế này:
function x() {
console.log('x');
}
Một khai báo chức năng là một khai báo ; nó không phải là một tuyên bố hay biểu hiện. Như vậy, bạn không làm theo nó với một;
(mặc dù làm như vậy là vô hại).
Một khai báo hàm được xử lý khi thực thi đi vào ngữ cảnh mà nó xuất hiện, trước khi bất kỳ mã từng bước nào được thực thi. Hàm nó tạo ra được đặt tên thích hợp (x
trong ví dụ trên) và tên đó được đặt trong phạm vi mà khai báo xuất hiện.
Vì nó được xử lý trước bất kỳ mã từng bước nào trong cùng một ngữ cảnh, bạn có thể thực hiện những việc như sau:
x(); // Works even though it's above the declaration
function x() {
console.log('x');
}
Cho đến ES2015, spec không bao gồm những gì một công cụ JavaScript nên làm gì nếu bạn đặt một khai báo hàm bên trong một cấu trúc điều khiển như try
, if
, switch
, while
vv, như thế này:
if (someCondition) {
function foo() { // <===== HERE THERE
} // <===== BE DRAGONS
}
Và vì chúng được xử lý trước mã từng bước được chạy, thật khó để biết phải làm gì khi chúng ở trong cấu trúc điều khiển.
Mặc dù việc này không được chỉ định cho đến ES2015, nhưng đây là một phần mở rộng được phép để hỗ trợ khai báo hàm theo khối. Thật không may (và chắc chắn), các động cơ khác nhau đã làm những điều khác nhau.
Kể từ ES2015, thông số kỹ thuật cho biết phải làm gì. Trong thực tế, nó cung cấp ba điều riêng biệt để làm:
- Nếu ở chế độ lỏng lẻo không có trên trình duyệt web, công cụ JavaScript có nghĩa vụ phải làm một việc
- Nếu ở chế độ lỏng trên trình duyệt web, công cụ JavaScript có nghĩa vụ phải làm một việc khác
- Nếu ở chế độ nghiêm ngặt (trình duyệt hoặc không), công cụ JavaScript có nghĩa vụ phải làm một việc khác
Các quy tắc cho các chế độ lỏng lẻo rất khó, nhưng ở chế độ nghiêm ngặt , việc khai báo hàm trong các khối rất dễ dàng: Chúng cục bộ với khối (chúng có phạm vi khối , cũng mới trong ES2015) và chúng được đưa lên đầu của khối. Vì thế:
"use strict";
if (someCondition) {
foo(); // Works just fine
function foo() {
}
}
console.log(typeof foo); // "undefined" (`foo` is not in scope here
// because it's not in the same block)
function
Biểu hiện "ẩn danh"
Dạng phổ biến thứ hai được gọi là biểu thức hàm ẩn danh :
var y = function () {
console.log('y');
};
Giống như tất cả các biểu thức, nó được đánh giá khi nó đạt được trong quá trình thực thi mã từng bước.
Trong ES5, chức năng mà trình tạo này không có tên (nó ẩn danh). Trong ES2015, hàm được gán tên nếu có thể bằng cách suy ra nó từ ngữ cảnh. Trong ví dụ trên, tên sẽ là y
. Một cái gì đó tương tự được thực hiện khi hàm là giá trị của bộ khởi tạo thuộc tính. (Để biết chi tiết về thời điểm điều này xảy ra và các quy tắc, hãy tìm kiếm SetFunctionName
trong thông số kỹ thuật - nó xuất hiện ở mọi nơi.)
function
Biểu hiện được đặt tên
Dạng thứ ba là một biểu thức hàm được đặt tên ("NFE"):
var z = function w() {
console.log('zw')
};
Hàm này tạo ra có một tên thích hợp ( w
trong trường hợp này). Giống như tất cả các biểu thức, điều này được đánh giá khi nó đạt được trong quá trình thực thi mã từng bước. Tên của hàm không được thêm vào phạm vi mà biểu thức xuất hiện; Tên nằm trong phạm vi trong chính chức năng:
var z = function w() {
console.log(typeof w); // "function"
};
console.log(typeof w); // "undefined"
Lưu ý rằng NFE thường là nguồn gây ra lỗi khi triển khai JavaScript. Ví dụ, IE8 và trước đó, xử lý NFE hoàn toàn không chính xác , tạo hai chức năng khác nhau ở hai thời điểm khác nhau. Các phiên bản đầu của Safari cũng có vấn đề. Tin vui là các phiên bản hiện tại của trình duyệt (IE9 trở lên, Safari hiện tại) không còn gặp phải những vấn đề đó nữa. (Nhưng về văn bản này, thật đáng buồn, IE8 vẫn được sử dụng rộng rãi và vì vậy việc sử dụng NFE với mã cho web nói chung vẫn còn có vấn đề.)
Trình khởi tạo chức năng Accessor (ES5 +)
Đôi khi các chức năng có thể lẻn vào phần lớn không được chú ý; đó là trường hợp với các hàm accessor . Đây là một ví dụ:
var obj = {
value: 0,
get f() {
return this.value;
},
set f(v) {
this.value = v;
}
};
console.log(obj.f); // 0
console.log(typeof obj.f); // "number"
Lưu ý rằng khi tôi sử dụng chức năng, tôi đã không sử dụng ()
! Đó là bởi vì đó là chức năng truy cập cho một tài sản. Chúng tôi nhận và thiết lập tài sản theo cách thông thường, nhưng đằng sau hậu trường, chức năng được gọi.
Bạn cũng có thể tạo các hàm accessor với Object.defineProperty
, Object.defineProperties
và đối số thứ hai ít được biết đến Object.create
.
Biểu thức chức năng mũi tên (ES2015 +)
ES2015 mang đến cho chúng ta chức năng mũi tên . Đây là một ví dụ:
var a = [1, 2, 3];
var b = a.map(n => n * 2);
console.log(b.join(", ")); // 2, 4, 6
Xem đó n => n * 2
điều đó ẩn trong map()
cuộc gọi? Đó là một chức năng.
Một vài điều về chức năng mũi tên:
Họ không có cái riêng của họ this
. Thay vào đó, họ gần hơn với this
bối cảnh nơi chúng được xác định. (Họ cũng đóng cửa arguments
và, khi có liên quan , super
.) Điều này có nghĩa là this
bên trong họ giống nhưthis
nơi chúng được tạo và không thể thay đổi.
Như bạn đã nhận thấy ở trên, bạn không sử dụng từ khóa function
; thay vào đó, bạn sử dụng =>
.
Các n => n * 2
ví dụ trên là một trong những hình thức của họ. Nếu bạn có nhiều đối số để truyền hàm, bạn sử dụng parens:
var a = [1, 2, 3];
var b = a.map((n, i) => n * i);
console.log(b.join(", ")); // 0, 2, 6
(Hãy nhớ rằng Array#map
vượt qua mục nhập làm đối số đầu tiên và chỉ mục là đối số thứ hai.)
Trong cả hai trường hợp, phần thân của hàm chỉ là một biểu thức; giá trị trả về của hàm sẽ tự động là kết quả của biểu thức đó (bạn không sử dụng rõ ràng return
).
Nếu bạn đang làm nhiều hơn chỉ một biểu thức, hãy sử dụng {}
và rõ ràng return
(nếu bạn cần trả về một giá trị), như bình thường:
var a = [
{first: "Joe", last: "Bloggs"},
{first: "Albert", last: "Bloggs"},
{first: "Mary", last: "Albright"}
];
a = a.sort((a, b) => {
var rv = a.last.localeCompare(b.last);
if (rv === 0) {
rv = a.first.localeCompare(b.first);
}
return rv;
});
console.log(JSON.stringify(a));
Phiên bản không có { ... }
được gọi là hàm mũi tên có thân biểu thức hoặc thân ngắn gọn . (Ngoài ra: Hàm mũi tên ngắn gọn .) Hàm có { ... }
xác định thân là hàm mũi tên có thân hàm . (Ngoài ra: Một chức năng mũi tên dài dòng .)
Khai báo phương thức trong Trình khởi tạo đối tượng (ES2015 +)
ES2015 cho phép một hình thức khai báo một thuộc tính ngắn hơn tham chiếu đến một hàm gọi là định nghĩa phương thức ; nó trông như thế này:
var o = {
foo() {
}
};
gần như tương đương trong ES5 và trước đó sẽ là:
var o = {
foo: function foo() {
}
};
sự khác biệt (khác với mức độ dài) là một phương thức có thể sử dụng super
, nhưng một chức năng thì không thể. Vì vậy, ví dụ, nếu bạn có một đối tượng xác định (nói) valueOf
sử dụng cú pháp phương thức, thì nó có thể sử dụng super.valueOf()
để lấy giá trị Object.prototype.valueOf
sẽ trả về (trước khi có thể làm gì đó với nó), trong khi phiên bản ES5 sẽ phải Object.prototype.valueOf.call(this)
thay thế.
Điều đó cũng có nghĩa là phương thức có tham chiếu đến đối tượng mà nó được định nghĩa, vì vậy nếu đối tượng đó là tạm thời (ví dụ: bạn chuyển nó vào Object.assign
như một trong các đối tượng nguồn), cú pháp phương thức có thể có nghĩa là đối tượng được giữ lại trong bộ nhớ khi không nó có thể là rác được thu thập (nếu công cụ JavaScript không phát hiện ra tình huống đó và xử lý nó nếu không có phương pháp nào sử dụngsuper
).
Trình xây dựng và khai báo phương thức trong class
(ES2015 +)
ES2015 mang đến cho chúng ta class
cú pháp, bao gồm các hàm tạo và phương thức khai báo:
class Person {
constructor(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
getFullName() {
return this.firstName + " " + this.lastName;
}
}
Có hai khai báo hàm ở trên: Một cho hàm tạo, lấy tên Person
và một cho getFullName
, là hàm được gán cho Person.prototype
.