+3

Differences between Arrow and Regular functions in Javascript

Hello mọi người, tiếp tục seri tìm hiểu về Javascript, hôm nay mình sẽ cùng tìm hiểu về những điểm khác nhau giữa Arrow Function và Regular Function trong Javascript nha. Ok, bắt đầu thôi.

Trong Javascript thì bạn có thể định nghĩa một function thoe nhiều cách khác nhau.

Cách đầu tiên đó là sử dụng function keyword:

// Function declaration
function greet(who) {
  return `Hello, ${who}!`;
}
// Function expression
const greet = function(who) {
  return `Hello, ${who}`;
}

Mình xin phép gọi chung Function declarationFunction expressionRegular function.

Cách tiếp theo để định nghĩa một function mới là sử dụng Arrow function syntax (bắt đầu khả dụng từ ES2015):

const greet = (who) => {
  return `Hello, ${who}!`;
}

Nếu cả hai cách sử dụng Arrow functionRegular function đều có thể định nghĩa function thì câu hỏi đặt ra là: Khi nào thì bạn sẽ sử dụng cách định nghĩa này thay vì cách khác?

Trong bài viết ngày hôm nay chúng ta cùng đi tìm hiểu điểm khác biệt giữa hai cách và bạn có thể lựa chọn cách tốt nhất cho các tình huống mà bạn gặp phải.

1. this value

1.1 Regular function

Bên trong Regular function thì giá trị thisdynamic. Dynamic có nghĩa là giá trị của this sẽ phụ thuộc vào việc function được thực thi như thế nào. Trong Javascript, có 4 cách để thực thi một Regular function.

Trong trường hợp sử dụng sinple invocation thì giá trị của this sẽ là global object.

function myFunction() {
  console.log(this);
}

// Simple invocation
myFunction(); // logs global object (window)

Đối với trường hợp sử dụng method invocation thì giá trị của this là object mà sở hữu method đó:

const myObject = {
  method() {
    console.log(this);
  }
};
// Method invocation
myObject.method(); // logs myObject

Đối với trường hợp sử dụng indirect invocation sử dụng myFunc.call(thisVal, arg1,..., argN) hoặc myFunc.apply(thisVal, [arg1,..., argN]) thì giá trị của this sẽ bằng đối số đầu tiên truyền vào:

function myFunction() {
  console.log(this);
}

const myContext = { value: 'A' };

myFunction.call(myContext);  // logs { value: 'A' }
myFunction.apply(myContext); // logs { value: 'A' }

Còn đối với constructor invocation sử dụng keyword new thì this sẽ bằng với instance mới được tạo:

function MyFunction() {
  console.log(this);
}

new MyFunction(); // logs an instance of MyFunction

1.2 Arrow function

this trong Arrow function nó những khác biệt đáng kể so với Regular function. Arrow function không xác định ngữ cảnh thực thi của riêng nó.

Không quan trọng hàm được thực thi ở đâu hay thực thi khi nào, giá trị của thistrong một arrow function luôn bằng this của function bao bọc nó (outer function).

Trong ví dụ dưới đây, myMethod() là một outer function của callback() arrow function:

const myObject = {
  myMethod(items) {
    console.log(this); // logs myObject
    const callback = () => {
      console.log(this); // logs myObject
    };
    items.forEach(callback);
  }
};

myObject.myMethod([1, 2, 3]);

giá trị của this trong arrow function callback() bằng giá trị của this của function myMethod().

Đây là một tính năng tuyệt vời của Arrow function. Khi sử dụng callback trong method bạn có thể chắc chắn là Arrow function sẽ không định nghĩa ra this của riêng nó và tất nhiên không cần sử dụng const self = this hoặc là callback.bind(this) 😃.

Trái ngược với Regular function, inderect invocation một Arrow function sử dụng myArrowFunc.call(thisVal) hoặc myArrowFunc.apply(thisVal) sẽ không thay đổi giá trị của this. Giá trị của nó sẽ luôn bằng giá trị của this của outer function.

Constructors

2.1 Regular function

Như chúng ta đã thấy ở phần trước, Regular function có thể dễ dàng khởi tạo một object.

Ví dụ, new Car() sẽ tạo ra một instance của Car:

function Car(color) {
  this.color = color;
}

const redCar = new Car('red');
redCar instanceof Car; // => true

Car là một Regular function. Khi khởi tạo với keyword new: new Car('red') một instance của Car sẽ được tạo mới.

2.3 Arrow function

Khác biệt so với Regular function thì Arrow function không thể sử dụng để khởi tạo một instance mới.

Nếu bạn cố gắng khởi tạo một Arrow function với keyword new Javasctipt sẽ bắt lỗi ngay lập tức:

const Car = (color) => {
  this.color = color;
};

const redCar = new Car('red'); // TypeError: Car is not a constructor 

Khi khởi tạo new Car('red') trong khi Car là một Arrow function sẽ bị lỗi: TypeError: Car is not a constructor.

3. Arguments object

3.1 Regular function

Bên trong body của một Regular function, arguments là một array-like object đặc biệt chứa danh sách các arguments mà function sẽ thực thi.

Ví dụ chúng ta sẽ thực thi myFunction() với 2 arguments:

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

myFunction('a', 'b'); // logs { 0: 'a', 1: 'b', length: 2 }

Bên trong myFunction() body thì arguments như là một array object bao gồm hai phần tử là 'a' và 'b'.

3.2 Arrow function

Mặt khác, không có keyword arguments được định nghĩa bên trong một Arrow fucntion.

Cũng giống như this ở phần 1, argumentsbên trong Arrow function sẽ được truy cập đến arguments của outer function.

Cùng xem một ví dụ:

function myRegularFunction() {
  const myArrowFunction = () => {
    console.log(arguments);
  }

  myArrowFunction('c', 'd');
}

myRegularFunction('a', 'b'); // logs { 0: 'a', 1: 'b', length: 2 }

Arrow function myArrowFunction() được thực thi với hai đối số 'c', 'd'. Nhưng bên trong body của myArrowFunction(), arguments object bằng với arguments object của myRegularFunction(): 'a', 'b'.

Nếu như bạn muốn truy cập vào đối số của Arrow function bạn cần sử dụng rest parameters:

function myRegularFunction() {
  const myArrowFunction = (...args) => {
    console.log(args);
  }

  myArrowFunction('c', 'd');
}

myRegularFunction('a', 'b'); // logs ['c', 'd']

...args parameter sẽ lấy tất cả đối số của Arrow function: ['c', 'd']

4. Implicit return

4.1 Regular function

return sẽ trả về kết quả của function:

function myFunction() {
  return 42;
}

myFunction(); // => 42

Nếu một hàm không có return hoặc là không có giá trị gì truyền vào return thì Regular function sẽ trả về undefined:

function myEmptyFunction() {
  42;
}

function myEmptyFunction2() {
  42;
  return;
}

myEmptyFunction();  // => undefined
myEmptyFunction2(); // => undefined

4.2 Arrow function

Nếu bạn return về một giá trị trong Arrow function thì nó cũng sẽ tương tự như Regular function, nhưng có một trường hợp khác biệt so với Regular function.

Khi sử dụng inline arrow function, nó sẽ tự động return về kết quả:

const increment = (num) => num + 1;

increment(41); // => 42

increment() function sử dụng duy nhất một expression: num + 1. Khi đó giá trị của num + 1 sẽ được tự động trả về khi gọi đến increment(41) mà không cần return.

5. Methods

5.1 Regular function

Regular function là một cách thông thường để định nghĩa một method trong class.

Trong class Hero, method logName() được định nghĩa bằng cách sử dụng regular function:

class Hero {
  constructor(heroName) {
    this.heroName = heroName;
  }

  logName() {
    console.log(this.heroName);
  }
}

const batman = new Hero('Batman');

Thông thường, regular function sẽ được sử dụng để định nghĩa một method trong class.

Đôi khi bạn cần sử dụng một callback, ví dụ như setTimeout() hoặc là event listener. Trong trường hợp này bạn có thể bỏ sót this.

Ví dụ, chúng ta hãy thử sử dụng logName() method như là một callback của setTimeout():

setTimeout(batman.logName, 1000);
// after 1 second logs "undefined"

Sau một giây, undefined sẽ được log trong console. setTimeout() thực thi một lời gọi hàm của logName (nơi mà this là một global object). Đó là khi mà method sẽ tách biệt với object.

Cùng sử dụng bind cho this:

setTimeout(batman.logName.bind(batman), 1000);
// after 1 second logs "Batman"

batman.logName.bind(batman) sẽ bind giá trị của this từ batman instance.

Bind this thủ công như vậy là không tốt chút nào, đặc biệt là khi bạn có rất nhiều method. Nhưng có một cách tuyệt vời hơn: sử dụng Arrow function như là một class field.

5.2 Arrow function

Với class field, bạn có thể sử dụng Arrow function như là một method bên trong class.

Cùng đi tới một ví dụ sử dụng Arrow function như là một field trong class:

class Hero {
  constructor(heroName) {
    this.heroName = heroName;
  }

  logName = () => {
    console.log(this.heroName);
  }
}

const batman = new Hero('Batman');

Bây giờ bạn có thể sử dụng batman.logName như là một callback mà không cần sử dụng binding. Giá trịc của this bên trong logName() luôn luôn là class instance:

setTimeout(batman.logName, 1000);
// after 1 second logs "Batman"

6. Summary

Khi hiểu được sự khác nhau giữa Regular function và Arrow function sẽ giúp bạn lựa chọn chính xác nên sử dụng cách nào trong từng trường hợp khác nhau.

this trong Regular function là không cố định, giá trị của nó phụ thuộc vào việc thực thi. Nhưng this bên trong Arrow function thì bằng giá trị của this của outer function.

arguments object trong Regular function chứa dánh sách các đối số của function. Ngược lại, đối với Arrow function, nó không định nghĩa arguments (nhưng bạn có thể dễ dàng truy cập vào nó bằng cách sử dụng ...args).

Nếu Arrow function có một expression, khi đó giá trị của espression sẽ được trả về mà không cần sử dụng keyword return.

Cuối cùng nhưng cũng không kém phần quan trọng, bạn có thể định nghĩa một method sử dụng Arrow function bên trong một class. Arrow method sẽ tự động bind this của instance class.

Bằng bất cứ cách nào, khi Arrow method được thực thi, this luôn bằng class instance, nó rất hữu dụng khi method sử dụng callback.

Để hiểu rõ hơn tất cả các kiểu function trong Javascript, bạn có thể tham khảo 6 Ways to Declare JavaScript Functions

7. References


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí