Cách sử dụng Arrow Function trong JavaScript

Javascript căn bản | by Học Javascript

Arrow Function là một tính năng quan trọng được giới thiệu trong ES6 (ECMAScript 2015), giúp viết các hàm JavaScript ngắn gọn và dễ đọc hơn. So với function thông thường, Arrow Function có cú pháp tối giản hơn, loại bỏ nhu cầu sử dụng từ khóa function, đồng thời tự động kế thừa this từ phạm vi chứa nó. Điều này giúp giải quyết nhiều vấn đề phổ biến khi làm việc với JavaScript, đặc biệt trong các callback function, phương thức mảng (map(), filter(), reduce()), hoặc trong lập trình bất đồng bộ với setTimeout(), setInterval().

Tuy nhiên, Arrow Function cũng có những hạn chế như không thể sử dụng làm constructor, không có đối tượng arguments riêng, và không thích hợp để làm phương thức của object. Vì vậy, để sử dụng hiệu quả, lập trình viên cần hiểu rõ cách hoạt động của Arrow Function và khi nào nên hoặc không nên sử dụng nó.

Trong bài viết này, mình sẽ cùng tìm hiểu cách khai báo và sử dụng Arrow Function, so sánh với function truyền thống, cũng như những tình huống phù hợp để áp dụng nó trong lập trình JavaScript.

Arrow Function trong JavaScript là gì?

Arrow Function là một kiểu khai báo hàm mới trong JavaScript, được giới thiệu từ ES6 (ECMAScript 2015), giúp viết hàm một cách ngắn gọn hơn. Thay vì sử dụng từ khóa function, Arrow Function sử dụng ký hiệu "=>" (mũi tên) để định nghĩa một hàm.

Cú pháp của Arrow Function:

const tenHam = (thamSo1, thamSo2) => {
    // Thân hàm
    return thamSo1 + thamSo2;
};

Hoặc nếu hàm chỉ có một biểu thức, ta có thể viết ngắn gọn:

const cong = (a, b) => a + b;
console.log(cong(2, 3)); // Output: 5

Lưu ý: Nếu chỉ có một tham số, ta có thể bỏ dấu ngoặc tròn () như sau:

const inTen = ten => console.log("Tên của bạn là: " + ten);
inTen("Kiên"); // Output: Tên của bạn là: Kiên

Lịch sử ra đời của Arrow Function

Trước khi có Arrow Function, JavaScript chỉ có cách khai báo hàm bằng function. Tuy nhiên, việc sử dụng function có một số vấn đề, đặc biệt là về phạm vi của this.

Ví dụ, khi sử dụng setTimeout() hoặc phương thức mảng như map(), filter(), đôi khi this không hoạt động như mong đợi:

function Person(name) {
    this.name = name;
    
    setTimeout(function() {
        console.log("Tên là: " + this.name); 
    }, 1000);
}

const p = new Person("Kiên");
// Output: "Tên là: undefined" (do this không trỏ đến Person)

Trong ES6, Arrow Function được giới thiệu nhằm giải quyết vấn đề trên bằng cách kế thừa this từ phạm vi cha thay vì tạo một this mới:

function Person(name) {
    this.name = name;
    
    setTimeout(() => {
        console.log("Tên là: " + this.name);
    }, 1000);
}

const p = new Person("Kiên");
// Output: "Tên là: Kiên" (do Arrow Function kế thừa this từ Person)

Ưu điểm của Arrow Function so với Function thông thường

Arrow Function mang lại nhiều lợi ích khi lập trình với JavaScript, đặc biệt trong các tình huống cần xử lý hàm ngắn gọn và không cần thay đổi phạm vi this.

Cú pháp ngắn gọn hơn

Arrow Function giúp loại bỏ từ khóa function, return (nếu chỉ có một dòng lệnh) và {} (nếu chỉ có một biểu thức).
Ví dụ:

// Hàm thông thường
function binhPhuong(x) {
    return x * x;
}

// Viết lại bằng Arrow Function
const binhPhuong = x => x * x;

console.log(binhPhuong(4)); // Output: 16

Không tạo this riêng, kế thừa this từ phạm vi cha

Arrow Function không có this riêng mà sẽ kế thừa this từ phạm vi chứa nó. Điều này giúp tránh lỗi khi this bị thay đổi không mong muốn.
Ví dụ với setTimeout():

// Dùng function thông thường (lỗi this)
function User(name) {
    this.name = name;

    setTimeout(function() {
        console.log(this.name);
    }, 1000);
}

const user1 = new User("Nam"); // Output: undefined

// Dùng Arrow Function (hoạt động đúng)
function User(name) {
    this.name = name;

    setTimeout(() => {
        console.log(this.name);
    }, 1000);
}

const user2 = new User("Nam"); // Output: "Nam"

Không có đối tượng arguments

Arrow Function không có đối tượng arguments, điều này giúp tránh lỗi không mong muốn khi làm việc với các hàm nhận nhiều tham số.
Ví dụ, trong hàm thông thường:

function showArguments() {
    console.log(arguments);
}

showArguments(1, 2, 3); 
// Output: [Arguments] { '0': 1, '1': 2, '2': 3 }

Nhưng nếu dùng Arrow Function:

const showArguments = () => {
    console.log(arguments);
};

showArguments(1, 2, 3); 
// Lỗi: arguments is not defined
Để thay thế arguments, ta có thể dùng toán tử rest (...):
const showArguments = (...args) => {
    console.log(args);
};

showArguments(1, 2, 3); 
// Output: [ 1, 2, 3 ]

Dễ sử dụng trong các phương thức mảng như map(), filter(), reduce()

Với các phương thức mảng, Arrow Function giúp code dễ đọc và gọn hơn:

const numbers = [1, 2, 3, 4];

// Dùng function thông thường
const squares1 = numbers.map(function(n) {
    return n * n;
});

// Dùng Arrow Function
const squares2 = numbers.map(n => n * n);

console.log(squares2); // Output: [1, 4, 9, 16]

Dễ đọc và dễ bảo trì hơn

Việc loại bỏ từ khóa function, return, {} giúp mã nguồn sạch hơn và dễ hiểu hơn.

Cách khai báo và sử dụng Arrow Function trong JavaScript

Cú pháp cơ bản

Arrow Function có cú pháp ngắn gọn hơn so với function thông thường. Thay vì sử dụng từ khóa function, ta sử dụng ký hiệu => (mũi tên).

Cú pháp Arrow Function:

const tenHam = (thamSo1, thamSo2) => {
    // Thân hàm
    return thamSo1 + thamSo2;
};

So sánh Arrow Function với Function thông thường:

Ví dụ 1: Hàm cộng hai số

Function thông thường:

function cong(a, b) {
    return a + b;
}

console.log(cong(3, 5)); // Output: 8

Arrow Function tương đương:

const cong = (a, b) => a + b;

console.log(cong(3, 5)); // Output: 8

Arrow Function giúp loại bỏ từ khóa function và từ khóa return khi chỉ có một biểu thức trả về.

Rút gọn cú pháp với Arrow Function

Arrow Function có thể được rút gọn tùy vào số lượng tham số và số dòng lệnh trong thân hàm.

Khi có một tham số → Bỏ dấu ngoặc tròn ()

// Function thông thường
function inTen(ten) {
    return `Tên của bạn là ${ten}`;
}

// Viết bằng Arrow Function
const inTen = ten => `Tên của bạn là ${ten}`;

console.log(inTen("Kiên")); // Output: "Tên của bạn là Kiên"

Khi có một dòng lệnh → Bỏ dấu {}return

// Function thông thường
function binhPhuong(x) {
    return x * x;
}

// Viết bằng Arrow Function
const binhPhuong = x => x * x;

console.log(binhPhuong(4)); // Output: 16

Arrow Function không có this riêng

Arrow Function không tạo ra phạm vi this mới mà kế thừa this từ phạm vi bên ngoài (lexical this). Điều này giúp tránh một số lỗi phổ biến khi sử dụng this trong JavaScript.

So sánh this trong Function thông thường và Arrow Function

Function thông thường (lỗi this)

function User(name) {
    this.name = name;

    setTimeout(function() {
        console.log(this.name);
    }, 1000);
}

const user1 = new User("Nam"); // Output: undefined

Lỗi xảy ra vì function trong setTimeout() tạo ra this mới, không trỏ đến User.

Arrow Function (hoạt động đúng)

function User(name) {
    this.name = name;

    setTimeout(() => {
        console.log(this.name);
    }, 1000);
}

const user2 = new User("Nam"); // Output: Nam

Arrow Function không có this riêng nên kế thừa this từ User, tránh lỗi.

Arrow Function không có arguments

Arrow Function không có đối tượng arguments riêng mà kế thừa từ phạm vi bên ngoài.

Function thông thường có arguments

function showArguments() {
    console.log(arguments);
}

showArguments(1, 2, 3); 
// Output: [Arguments] { '0': 1, '1': 2, '2': 3 }

Arrow Function KHÔNG có arguments (lỗi)

const showArguments = () => {
    console.log(arguments);
};

showArguments(1, 2, 3); 
// Lỗi: arguments is not defined
Cách thay thế arguments bằng ...rest
const showArguments = (...args) => {
    console.log(args);
};

showArguments(1, 2, 3); 
// Output: [ 1, 2, 3 ]

Arrow Function trong các phương thức của Object

Arrow Function không nên dùng làm phương thức của object vì this không trỏ đến object mà trỏ đến phạm vi bên ngoài.

Lỗi khi sử dụng Arrow Function trong object

const person = {
    name: "Nam",
    sayHello: () => {
        console.log("Xin chào, tôi là " + this.name);
    }
};

person.sayHello(); // Output: Xin chào, tôi là undefined

Lỗi xảy ra vì this trong Arrow Function không trỏ đến person mà trỏ đến phạm vi global.

Cách dùng function thông thường (đúng)

const person = {
    name: "Nam",
    sayHello: function() {
        console.log("Xin chào, tôi là " + this.name);
    }
};

person.sayHello(); // Output: Xin chào, tôi là Nam

Arrow Function trong Callback Function

Arrow Function thường được sử dụng trong các callback function để tránh lỗi this.

Trong setTimeout()setInterval()

const user = {
    name: "Huy",
    greet: function() {
        setTimeout(() => {
            console.log("Xin chào, tôi là " + this.name);
        }, 1000);
    }
};

user.greet(); // Output: Xin chào, tôi là Huy

Trong map(), filter(), reduce()

const numbers = [1, 2, 3, 4];

// Function thông thường
const squares1 = numbers.map(function(n) {
    return n * n;
});

// Arrow Function
const squares2 = numbers.map(n => n * n);

console.log(squares2); // Output: [1, 4, 9, 16]

Arrow Function trong Class và Constructor

Arrow Function không thể dùng làm Constructor (lỗi)

const Person = (name) => {
    this.name = name;
};

const p = new Person("Nam"); // Lỗi: Person is not a constructor

Lỗi xảy ra vì Arrow Function không có this riêng nên không thể dùng với new.

Khi nào nên dùng Arrow Function trong Class?

Arrow Function hữu ích khi dùng trong phương thức của class để giữ nguyên this.

class Person {
    constructor(name) {
        this.name = name;
    }

    greet = () => {
        console.log("Xin chào, tôi là " + this.name);
    };
}

const p = new Person("Nam");
p.greet(); // Output: Xin chào, tôi là Nam

Lưu ý khi sử dụng Arrow Function trong JavaScript

Mặc dù Arrow Function mang lại nhiều lợi ích về cú pháp ngắn gọn và cách hoạt động của this, nhưng không phải lúc nào nó cũng phù hợp để sử dụng. Dưới đây là những trường hợp cần lưu ý khi sử dụng Arrow Function.

Khi không nên sử dụng Arrow Function

Khi cần một phương thức trong Object

Arrow Function không tạo ra this riêng, điều này gây ra lỗi khi sử dụng nó làm phương thức của object.

Lỗi khi dùng Arrow Function làm phương thức

const person = {
    name: "Huy",
    sayHello: () => {
        console.log("Xin chào, tôi là " + this.name);
    }
};

person.sayHello(); // Output: Xin chào, tôi là undefined

Lỗi xảy ra vì this trong Arrow Function không trỏ đến person, mà trỏ đến phạm vi bên ngoài (global object).

Cách đúng: Dùng function thông thường
const person = {
    name: "Huy",
    sayHello: function() {
        console.log("Xin chào, tôi là " + this.name);
    }
};

person.sayHello(); // Output: Xin chào, tôi là Huy

Khi cần sử dụng arguments

Arrow Function không có đối tượng arguments, điều này gây lỗi khi muốn truy cập danh sách tham số động.

Lỗi khi sử dụng arguments trong Arrow Function

const showArgs = () => {
    console.log(arguments);
};

showArgs(1, 2, 3); // Lỗi: arguments is not defined

Cách đúng: Dùng function thông thường hoặc ...rest

function showArgs() {
    console.log(arguments);
}

showArgs(1, 2, 3); // Output: [Arguments] { '0': 1, '1': 2, '2': 3 }

// Hoặc dùng rest parameter trong Arrow Function
const showArgsArrow = (...args) => {
    console.log(args);
};

showArgsArrow(1, 2, 3); // Output: [1, 2, 3]

Khi cần dùng làm Constructor Function

Arrow Function không thể được dùng với new vì nó không có this riêng.

Lỗi khi dùng Arrow Function làm Constructor

const Person = (name) => {
    this.name = name;
};

const p = new Person("Nam"); // Lỗi: Person is not a constructor

Cách đúng: Dùng function thông thường hoặc class

function Person(name) {
    this.name = name;
}

const p = new Person("Nam");
console.log(p.name); // Output: Nam

// Hoặc dùng class
class PersonClass {
    constructor(name) {
        this.name = name;
    }
}

const p2 = new PersonClass("Huy");
console.log(p2.name); // Output: Huy

Khi cần truy cập thuộc tính prototype

Arrow Function không có thuộc tính prototype, vì vậy không thể dùng để khai báo method trong prototype của một Object.

Lỗi khi dùng Arrow Function làm prototype method

function Person(name) {
    this.name = name;
}

Person.prototype.sayHello = () => {
    console.log("Xin chào, tôi là " + this.name);
};

const p = new Person("Nam");
p.sayHello(); // Output: Xin chào, tôi là undefined

Lỗi xảy ra vì Arrow Function không có this riêng, this.name là undefined.

Cách đúng: Dùng function thông thường

Person.prototype.sayHello = function() {
    console.log("Xin chào, tôi là " + this.name);
};

p.sayHello(); // Output: Xin chào, tôi là Nam

Arrow Function có thể gây lỗi trong một số trường hợp

Khi sử dụng trong Event Listener

Arrow Function không có this riêng, nên khi dùng trong Event Listener, this sẽ không trỏ đến phần tử HTML.

Lỗi khi dùng Arrow Function trong Event Listener

document.querySelector("#btn").addEventListener("click", () => {
    console.log(this); // Output: Window (không phải phần tử #btn)
});

Cách đúng: Dùng function thông thường

document.querySelector("#btn").addEventListener("click", function() {
    console.log(this); // Output: <button id="btn">...</button>
});

So sánh hiệu suất giữa Arrow Function và Function thông thường

Hiệu suất khi sử dụng nhiều Arrow Function

Trong hầu hết các trường hợp, Arrow Function và function thông thường có hiệu suất tương đương. Tuy nhiên, do Arrow Function không có this, argumentsprototype, nên nó có thể tiêu tốn ít bộ nhớ hơn trong một số trường hợp.

Hiệu suất trong vòng lặp

Khi sử dụng trong vòng lặp lớn, function thông thường có thể có lợi thế hơn nếu cần tối ưu hóa sâu.

Ví dụ: So sánh tốc độ chạy của Arrow Function và Function thông thường

console.time("Function");
for (let i = 0; i < 1000000; i++) {
    (function(x) { return x * x; })(i);
}
console.timeEnd("Function");

console.time("Arrow Function");
for (let i = 0; i < 1000000; i++) {
    ((x) => x * x)(i);
}
console.timeEnd("Arrow Function");

Trong các trình duyệt hiện đại, hiệu suất của cả hai loại function thường rất gần nhau.

Trường hợp Function thông thường Arrow Function
Sử dụng trong Object Hoạt động đúng Không có this, gây lỗi
Sử dụng arguments arguments Không có arguments
Dùng làm Constructor Được phép Không thể dùng với new
Dùng làm phương thức prototype Được phép Không thể dùng
Dùng trong Event Listener this trỏ đến phần tử this trỏ đến window
Hiệu suất Tốt khi cần tối ưu sâu Nhẹ hơn trong một số trường hợp

Khi nào nên dùng Arrow Function?
Khi cần một function nhỏ, gọn, không phụ thuộc vào this.
Khi sử dụng trong các callback như map(), filter(), reduce(), setTimeout().
Khi muốn giữ nguyên this từ phạm vi bên ngoài.

Khi nào KHÔNG nên dùng Arrow Function?
Khi cần dùng this trong object method hoặc event listener.
Khi cần arguments, prototype hoặc làm Constructor Function.

Kết bài

Arrow Function là một tính năng quan trọng của JavaScript được giới thiệu từ ES6, giúp code trở nên ngắn gọn và dễ đọc hơn. Với cú pháp đơn giản, Arrow Function đặc biệt hữu ích trong các tình huống như sử dụng trong callback (map(), filter(), reduce(), setTimeout(), v.v.) hoặc khi cần giữ nguyên ngữ cảnh của this

Tuy nhiên, Arrow Function không thể thay thế hoàn toàn function thông thường. Việc không có this riêng, không có arguments, không thể dùng làm constructor, và một số hạn chế khác khiến nó không phù hợp trong một số trường hợp, chẳng hạn như khi sử dụng trong object method hoặc event listener.

  • Sử dụng Arrow Function khi viết code ngắn gọn, đơn giản và không cần thay đổi this.
  • Dùng function thông thường khi cần xử lý this, arguments, hoặc khai báo phương thức trong object/class.
  • Hiểu rõ cách hoạt động của từng loại function sẽ giúp bạn viết code tối ưu và tránh lỗi không mong muốn.

Với sự linh hoạt của JavaScript, việc kết hợp cả Arrow Function và function thông thường một cách hợp lý sẽ giúp bạn xây dựng ứng dụng hiệu quả và dễ bảo trì hơn!

Bài viết liên quan