Hàm biểu thức rút gọn có cú pháp ngắn gọn hơn hàm biểu thức và không phụ thuộc vào chính nó thisargumentssuper, or new.target. Có nhiều hàm biểu thức áp dụng tốt cho hàm vô danh ( không có tên phương thức), nhưng chúng lại không thể sử dụng như một hàm khởi tạo.

Cú pháp

Cú pháp cơ bản

(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; }

Cú pháp nâng cao

// 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.

Mô tả

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.

Hàm ngắn

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);

 

Không ràng buộc 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();

Mối liên hệ với strict mode

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.

Gọi thông qua call hoặc apply

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

Không ràng buộc 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

Dùng hàm rút gọn như một phương thức

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 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'
  }
});

Dùng toán tử 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

Dùng thuộc tính prototype

Hàm rút gọn không có thuộc tính prototype.

var Foo = () => {};
console.log(Foo.prototype); // undefined

Dùng từ khoá 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).

Phần thân hàm

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

Trả về object literals

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});

Kí tự xuống dòng

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 '=>'

Parsing order

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

Một số ví dụ khác

// 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);  

Đặc điểm kĩ thuật

Specification Status Comment
ECMAScript 2015 (6th Edition, ECMA-262)
The definition of 'Arrow Function Definitions' in that specification.
Standard Initial definition.
ECMAScript Latest Draft (ECMA-262)
The definition of 'Arrow Function Definitions' in that specification.
Draft  

Tương thích trình duyệt

We're converting our compatibility data into a machine-readable JSON format. This compatibility table still uses the old format, because we haven't yet converted the data it contains. Find out how you can help!

Feature Chrome Firefox (Gecko) Edge IE Opera Safari
Basic support 45.0 22.0 (22.0) (Yes)

No support

32 10.0
Trailing comma in parameters ? 52.0 (52.0) ? ? ?
Feature Android Android Webview Firefox Mobile (Gecko) IE Mobile Opera Mobile Safari Mobile Chrome for Android
Basic support No support 45.0 22.0 (22.0) No support No support 10.0 45.0
Trailing comma in parameters ? ? 52.0 (52.0) ? ? ? ?

Firefox-specific notes

  • The initial implementation of arrow functions in Firefox made them automatically strict. This has been changed as of Firefox 24. The use of "use strict"; is now required.
  • Arrow functions are semantically different from the non-standard expression closures added in Firefox 3 (details: JavaScript 1.8), for expression closures do not bind this lexically.
  • Prior to Firefox 39, a line terminator (\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 SyntaxError in this and later versions.

Tìm đọc

Document Tags and Contributors

Những người đóng góp cho trang này: PurpleLover, thaiviptn1201, anhnc
Cập nhật lần cuối bởi: PurpleLover,