JavaScript cung cấp hai cách chính để định nghĩa hàm: cách truyền thống dùng từ khóa function() và một cú pháp mới hơn được giới thiệu trong ECMAScript 6 (ES6), được gọi là arrow function (()=>). Mặc dù cả hai đều tạo ra các hàm, chúng có những khác biệt quan trọng về cú pháp, cách xử lý this, và các tính năng nâng cao khác.

Trong bài viết này, chúng ta sẽ so sánh hai cách định nghĩa này để hiểu rõ sự khác biệt và khi nào nên sử dụng từng loại.

1. Cú pháp

  • Function Declaration (hàm truyền thống): đây là cách viết truyền thống bằng từ khóa function.
js
function sayHello() {
    return "Hello, World!";
}
  • Arrow Function (hàm mũi tên): cú pháp ngắn gọn hơn, không cần sử dụng từ khóa function. Hàm mũi tên sử dụng ký hiệu => để định nghĩa hàm.
js
const sayHello = () => {
    return "Hello, World!";
}
  • Đối với các hàm chỉ có một biểu thức và trả về kết quả trực tiếp, có thể bỏ qua từ khóa return và dấu ngoặc nhọn:
js
const sayHello = () => "Hello, World!";

2. Xử lý từ khóa this

Một trong những sự khác biệt quan trọng nhất giữa function() và arrow function là cách chúng xử lý từ khóa this.

  • Hàm truyền thống: Trong hàm truyền thống, this tham chiếu tới ngữ cảnh mà hàm được gọi, không phải ngữ cảnh mà hàm được định nghĩa. Do đó, giá trị của this có thể thay đổi tùy thuộc vào cách mà hàm được gọi.
js
function Person(name) {
    this.name = name;
}

Person.prototype.sayHello = function() {
    console.log(`Hello, my name is ${this.name}`);
}

const john = new Person("John");
john.sayHello(); // Output: Hello, my name is John
  • Arrow Function: Arrow function không cóthis của riêng nó. Thay vào đó, nó kế thừathis từ ngữ cảnh xung quanh tại thời điểm hàm được định nghĩa. Điều này rất hữu ích trong các tình huống mà hàm truyền thống gây ra lỗi khi sử dụng this.
js
function Person(name) {
    this.name = name;
    setTimeout(() => {
        console.log(`Hello, my name is ${this.name}`);
    }, 1000);
}

const john = new Person("John");
// Output sau 1 giây: Hello, my name is John
  • Trong ví dụ trên, nếu sử dụng hàm truyền thống trong setTimeout, giá trị của this sẽ là undefined hoặc tham chiếu sai vì this bị "mất" khi gọi trong một ngữ cảnh khác. Arrow function giữ nguyên giá trị this từ nơi nó được định nghĩa.

3. Khả năng sử dụng làm phương thức (Method)

  • Hàm truyền thống: Thường được sử dụng làm phương thức của đối tượng hoặc constructor function.
js
const obj = {
    name: "John",
    sayHello: function() {
        console.log(`Hello, my name is ${this.name}`);
    }
};

obj.sayHello(); // Output: Hello, my name is John

Arrow Function: Không phù hợp để làm phương thức của đối tượng vì không có this riêng. Khi được sử dụng làm phương thức, this trong arrow function sẽ tham chiếu tới ngữ cảnh toàn cục.

js
const obj = {
    name: "John",
    sayHello: () => {
        console.log(`Hello, my name is ${this.name}`);
    }
};

obj.sayHello(); // Output: Hello, my name is undefined

Trong ví dụ trên, arrow function không thể truy cập đúng đối tượng obj thông qua this.

4. Arguments Object

  • Hàm truyền thống: Có đối tượng đặc biệt gọi là arguments, chứa tất cả các tham số được truyền vào hàm.
js
function add() {
    console.log(arguments); // Logs all arguments passed to the function
    return arguments[0] + arguments[1];
}

add(2, 3); // Output: [2, 3]
  • Arrow Function: Không có đối tượng arguments. Để làm điều này, bạn phải sử dụng toán tử spread (...) để xử lý các tham số.
js
const add = (...args) => {
    console.log(args); // Logs all arguments passed to the function
    return args[0] + args[1];
};

add(2, 3); // Output: [2, 3]

5. Sử dụng làm Constructor

  • Hàm truyền thống: Có thể được sử dụng làm constructor (hàm dựng) để tạo ra các đối tượng bằng từ khóa new.
js
function Person(name) {
    this.name = name;
}

const john = new Person("John");
console.log(john.name); // Output: John
  • Arrow Function: Không thể được sử dụng làm constructor. Nếu cố gắng sử dụng new với arrow function, JavaScript sẽ ném ra lỗi.
js
const Person = (name) => {
    this.name = name;
};

const john = new Person("John"); // Error: Person is not a constructor

6. Tính năng nâng cao

  • Hàm truyền thống: Có thể sử dụng yield trong generator function, trong khi arrow function không hỗ trợ.
js
function* generatorFunction() {
    yield 1;
    yield 2;
    yield 3;
}

const gen = generatorFunction();
console.log(gen.next().value); // Output: 1
  • Arrow Function: Không hỗ trợ generator function, do đó không thể dùng yield.

7. Hiệu suất

Không có sự khác biệt lớn về hiệu suất giữa hai loại hàm này trong các ứng dụng thông thường. Tuy nhiên, với các tác vụ rất cụ thể và phức tạp, việc chọn cú pháp có thể ảnh hưởng đến hiệu suất tùy thuộc vào ngữ cảnh và môi trường.

8. Khi nào nên sử dụng?

  • Sử dụng hàm truyền thống:
    • Khi bạn cần tạo constructor.
    • Khi bạn cần sử dụng this trong ngữ cảnh thay đổi (ví dụ khi dùng hàm trong các đối tượng).
    • Khi cần dùng đối tượng arguments để truy cập tất cả các tham số được truyền vào.
    • Khi tạo generator function với từ khóa yield.
  • Sử dụng arrow function:
    • Khi cần một cú pháp ngắn gọn hơn, đặc biệt với các hàm không phức tạp.
    • Khi muốn this giữ nguyên giá trị từ ngữ cảnh bên ngoài, đặc biệt hữu ích khi xử lý callback hoặc hàm bên trong các phương thức.
    • Khi bạn không cần dùng đối tượng arguments hoặc làm constructor.

Kết luận

Mặc dù cả hàm truyền thống và arrow function đều được dùng để định nghĩa hàm, nhưng chúng có cách xử lý khác nhau về từ khóa this, đối tượng arguments, và khả năng sử dụng làm constructor. Arrow function thường ngắn gọn và phù hợp cho các hàm đơn giản hoặc trong trường hợp bạn muốn kế thừa this từ ngữ cảnh bên ngoài. Tuy nhiên, hàm truyền thống vẫn hữu ích trong các trường hợp phức tạp hơn, như làm constructor, xử lý ngữ cảnh động của this, hoặc sử dụng arguments.