--- title: Hàm mũi tên (hàm rút gọn) slug: Web/JavaScript/Reference/Functions/Arrow_functions translation_of: Web/JavaScript/Reference/Functions/Arrow_functions ---
Biểu thức hàm mũi tên là một thay thế rút gọn cho hàm biểu thức truyền thống, nhưng bị hạn chế và không thể sử dụng trong mọi trường hợp.
Sự khác biệt & Hạn chế:
this
hoặc super
, và không nên dùng ở methods
.arguments
, hoặc từ khóa new.target
.call
, apply
và bind
, thường dựa vào thiết lập scope
.constructors
.yield
, trong nội dung (body).{{EmbedInteractiveExample("pages/js/functions-arrow.html")}}
Hãy phân tách "hàm truyền thống" thành "hàm mũi tên" đơn giản nhất theo từng bước:
LƯU Ý: Mỗi bước là một "hàm mũi tên" hợp lệ.
// Hàm truyền thống function (a){ return a + 100; } // Phân rã thành hàm mũi tên // 1. Xóa từ khóa "function" và thay thế bằng mũi tên ở giữa đối số và dấu ngoặc nhọn bắt đầu nội dung hàm (a) => { return a + 100; } // 2. Xóa dấu ngoặc nhọn và từ khóa "return" sự trả về đã bao hàm (mặc định) khi sử dụng hàm mũi tên. (a) => a + 100; // 3. Xóa luôn dấu ngoặc đơn của đối số a => a + 100;
Như bạn thấy ở bên trên, dấu { ngoặc nhọn } và dấu (ngoặc tròn ở đối ố) và "return" là tùy chọn, nhưng đôi khi có thể bắt buộc phải có.
Ví dụ, nếu bạn có nhiều đối số hoặc không có đối số, bạn cần phải thêm dấu ngoặc tròn vào xung quanh các đối số:
// Hàm Truyền thống function (a, b){ return a + b + 100; } // Hàm mũi tên (a, b) => a + b + 100; // Hàm truyền thống (không đối số) let a = 4; let b = 2; function (){ return a + b + 100; } // Hàm mũi tên (không đối số) let a = 4; let b = 2; () => a + b + 100;
Tương tự như vậy, nếu nội dung (body) hàm cần thêm nhiều dòng để xử lý thì bạn cần thêm vào dấu ngoặc nhọn CỘNG thêm "return" (hàm mũi tên không có kỳ diệu đến mức biết khi nào bạn muốn "return"):
// Hàm truyền thống function (a, b){ let chuck = 42; return a + b + chuck; } // Hàm mũi tên (a, b) => { let chuck = 42; return a + b + chuck; }
Và cuối cùng, với các hàm được đặt tên chúng tôi xử lý các biểu thức mũi tên như các biến
// Hàm truyền thống function bob (a){ return a + 100; } // Hàm mũi tên let bob = a => a + 100;
(param1, param2, …, paramN) => { statements } (param1, param2, …, paramN) => expression // tương đương với: (param1, param2, …, paramN) => { return expression; } // Dấu ngoặc đơn không bắt buộc khi chỉ có một tham số truyền vào: (singleParam) => { statements } singleParam => { statements } // Hàm khi không có tham số truyền vào bắt buộc phải là dấu (): () => { statements } () => expression // tương đương: () => { return expression; }
// Bên trong dấu ngoặc đơn là một đối tượng: params => ({foo: bar}) // Rest parameters và default parameters được hỗ trợ (param1, param2, ...rest) => { statements } (param1 = defaultValue1, param2, …, paramN = defaultValueN) => { statements } // Destructuring within the parameter list is also supported var f = ([a, b] = [1, 2], {x: c} = {x: a + b}) => a + b + c; f(); // 6
Chi tiết các ví dụ bạn có thể xem ở đây.
Xem thêm "ES6 Chuyên sâu: Những hàm arrow" trên hacks.mozilla.org.
Two factors influenced the introduction of arrow functions: shorter functions and non-binding of this
.
Một vài ví dụ, cú pháp hàm rút gọn luôn được coder yêu thích, cùng so sánh:
var materials = [ 'Hydrogen', 'Helium', 'Lithium', 'Beryllium' ]; // thông thường var materialsLength1 = materials.map(function(material) { return material.length; }); // ngắn hơn (như mùa đông 5 độ vậy) var materialsLength2 = materials.map((material) => { return material.length; }); // ngắn hơn nữa (và -2 độ, bạn còn bao nhiêu cm ?) var materialsLength3 = materials.map(material => material.length);
this
Cho tới khi hàm rút gọn xuất hiện, mọi hàm mới đều tự định nghĩa giá trị this
của riêng hàm (là object vừa được khởi tạo nếu dùng constructor, là undefined nếu đặt strict mode khi gọi hàm, là context object nếu hàm được gọi như một "object method", vân vân.). This
trở nên khá khó chịu khi làm việc với phong cách lập trình hướng đối tượng.
function Person() { // constructor của Person() định nghĩa `this` như một biến. this.age = 0; setInterval(function growUp() { // Khi dùng non-strict mode, hàm growUp() định nghĩa `this` // như một đối tượng toàn cục, khác hoàn toàn so với `this` // được định nghĩa bởi constructor của Person(). this.age++; }, 1000); } var p = new Person();
Theo ECMAScript 3/5, vấn đề của this
có thể sửa được bằng cách gán this
cho một biến gần nhất.
function Person() { var that = this; that.age = 0; setInterval(function growUp() { // Hàm callback trỏ tới biến `that`với // giá trị là đối tượng mong đợi. that.age++; }, 1000); }
Nói cách khác, tạo ra một hàm ràng buộc để truyền giá trị của this
vào hàm ràng buộc đích (chẳng hạn như hàm growUp()
phía trên).
Hàm rút gọn không tạo ra ngữ cảnh this
của riêng hàm, thế nên this
có ý nghĩa trong ngữ cảnh bọc quanh nó. Đoạn code phía dưới là một ví dụ:
function Person(){ this.age = 0; setInterval(() => { this.age++; // |this| ở đây trỏ tới đối tượng person }, 1000); } var p = new Person();
Giả sử this
bị bó buộc trong thân hàm, strict mode sẽ khiến cho this
bị bỏ qua.
var f = () => { 'use strict'; return this; }; f() === window; // hoặc đối tượng toàn cục
Các strict mode khác được áp dụng như bình thường.
Vì this
không bị ràng buộc bên trong hàm rút gọn, các phương thức call()
hoặc apply()
chỉ có thể truyền tham số. this
bị bỏ qua.
var adder = { base: 1, add: function(a) { var f = v => v + this.base; return f(a); }, addThruCall: function(a) { var f = v => v + this.base; var b = { base: 2 }; return f.call(b, a); } }; console.log(adder.add(1)); // Sẽ trả ra 2 console.log(adder.addThruCall(1)); // Vẫn sẽ trả ra 2
arguments
Hàm rút gọn không ràng buộc arguments
object. Do đó, trong ví dụ sau, arguments
chỉ đơn giản là một tham chiếu đến đối tượng cùng tên trong phạm vi bao quanh:
var arguments = 42; var arr = () => arguments; arr(); // 42 function foo() { var f = (i) => arguments[0] + i; // foo's implicit arguments binding return f(2); } foo(1); // 3
Trong nhiều trường hợp, sử dụng rest parameters là một cách thay thế tốt để dùng đối tượng arguments
.
function foo() { var f = (...args) => args[0]; return f(2); } foo(1); // 2
Như đã nói phía trên, biểu thức hàm rút gọn cực kì hợp với các hàm non-method. Hãy xem chuyện gì sẽ xảy ra khi ta dùng chúng như phương thức trong ví dụ bên dưới nhé:
'use strict'; var obj = { i: 10, b: () => console.log(this.i, this), c: function() { console.log(this.i, this); } } obj.b(); // in ra undefined, Object {...} obj.c(); // in ra 10, Object {...}
Hàm rút gọn không định nghĩa ("ràng buộc") this
của hàm. Một ví dụ khác đối với {{jsxref("Object.defineProperty()")}}:
'use strict'; var obj = { a: 10 }; Object.defineProperty(obj, 'b', { get: () => { console.log(this.a, typeof this.a, this); return this.a + 10; // đại diện cho đối tượng toàn cục 'Window', bởi vậy 'this.a' trả về 'undefined' } });
new
Hàm rút gọn không thể dùng như phương thức khởi tạo và sẽ báo lỗi nếu dùng toán tử new
.
var Foo = () => {}; var foo = new Foo(); // TypeError: Foo is not a constructor
prototype
Hàm rút gọn không có thuộc tính prototype
.
var Foo = () => {}; console.log(Foo.prototype); // undefined
yield
Từ khoá yield
có thể sẽ không dùng được trong thân hàm rút gọn (trừ khi được gọi trong hàm lồng trong hàm rút gọn). Tức là, hàm rút gọn không thể dùng như là generator (hàm sinh).
Hàm rút gọn vừa có thể có "concise body" hoặc dạng thường thấy "block body".
Trong concise body, chỉ cần biểu thức, return sẽ được gán ngầm. Còn với block body, bạn phải có return
.
var func = x => x * x; // concise syntax, implied "return" var func = (x, y) => { return x + y; }; // with block body, explicit "return" needed
Không thể dùng cú pháp params => {object:literal}
nếu muốn trả về object literal.
var func = () => { foo: 1 }; // Calling func() returns undefined! var func = () => { foo: function() {} }; // SyntaxError: function statement requires a name
Bởi vì đoạn code bên trong ({}) được phân giải thành một chuỗi các trình tự nối tiếp (ví dụ foo
được coi như một nhãn, thay vì một key trong object literal).
Thế nên hãy bao object literal trong ngoặc tròn.
var func = () => ({foo: 1});
Hàm rút gọn không thể chứa bất cứ kí tự rút gọn nào giữa phần truyền tham số và dấu mũi tên.
var func = () => 1; // SyntaxError: expected expression, got '=>'
Although the arrow in an arrow function is not an operator, arrow functions have special parsing rules that interact differently with operator precedence compared to regular functions.
let callback; callback = callback || function() {}; // ok callback = callback || () => {}; // SyntaxError: invalid arrow-function arguments callback = callback || (() => {}); // ok
// An empty arrow function returns undefined let empty = () => {}; (() => 'foobar')(); // IIFE, returns "foobar" var simple = a => a > 15 ? 15 : a; simple(16); // 15 simple(10); // 10 let max = (a, b) => a > b ? a : b; // Easy array filtering, mapping, ... var arr = [5, 6, 13, 0, 1, 18, 23]; var sum = arr.reduce((a, b) => a + b); // 66 var even = arr.filter(v => v % 2 == 0); // [6, 0, 18] var double = arr.map(v => v * 2); // [10, 12, 26, 0, 2, 36, 46] // More concise promise chains promise.then(a => { // ... }).then(b => { // ... }); // Parameterless arrow functions that are visually easier to parse setTimeout( () => { console.log('I happen sooner'); setTimeout( () => { // deeper code console.log('I happen later'); }, 1); }, 1);
Specification | Status | Comment |
---|---|---|
{{SpecName('ES2015', '#sec-arrow-function-definitions', 'Arrow Function Definitions')}} | {{Spec2('ES2015')}} | Initial definition. |
{{SpecName('ESDraft', '#sec-arrow-function-definitions', 'Arrow Function Definitions')}} | {{Spec2('ESDraft')}} |
"use strict";
is now required.this
lexically.\n
) was incorrectly allowed after arrow function arguments. This has been fixed to conform to the ES2015 specification and code like () \n => {}
will now throw a {{jsxref("SyntaxError")}} in this and later versions.