Definition

Self-invoking function (hàm tự thực thi) có bản chất là các hàm vô danh (anonymous function1), được tự động truyền tham số và thực thi ngay khi chúng được khai báo. Self-invoking function còn có tên gọi khác là IIFE (Immediately Invoked Function Expression).

Nếu ta nhập lần lượt (123), ('Hello World'), ([1, 2, 3])(function (){}) vào console ở browser thì chúng sẽ tự động được in ra.

(123) // 123
('Hello World') // 'Hello World'
([1, 2, 3]) // [1, 2, 3]
(function(n){console.log(n)}) // ƒ (n){console.log(n)}

Có thể thấy, dòng lệnh (function (n){console.log(n)}) sẽ trả về một function, ta có thể dùng toán tử () để gọi function này.

Thay đổi một chút hàm ở trên

(function(n) {
	console.log(n) // 10
})(10)

Như vậy, ta đã có một self-invoking function.

Arrow Function as a Self-invoking Function

Cũng có thể dùng arrow function làm một self-invoking function:

((a, b) =>{
	let sum = a + b
	
	console.log(`Sum of ${a} and ${b} is ${sum}`) // Sum of 10 and 11 is 21
	
	return sum
})(10, 11)

Assign Return Value of Self-invoking Function

Ta có thể gán giá trị trả về của self-invoking function cho một biến:

let four = 4
 
let square = (function(n) {
	return n * n
})(four)
 
console.log(`Square of ${four} is ${square}`) // 16

Type Error

Xét đoạn code dưới đây:

let name = "Quan"
 
(function(){
	
}) // Uncaught TypeError: "Quan" is not a function

Ta đã biết toán tử () chính là toán tử gọi hàm, do đó, đoạn code trên khi đưa vào console nó sẽ hiểu là:

let name = "Quan"(function(){}) // Uncaught TypeError: "Quan" is not a function

Để sử dụng self-invoking trong trường hợp này, ta cần đặt dấu ; ở đầu self-invoking function:

let name = "Quan"
 
;(function(name){
	console.log(name)
})(name) // Quan

Scope of Self-invoking Function

Self-invoking function có phạm vi truy cập là “private”, hay nói cách khác là nó sẽ tạo ra một phạm vi riêng của nó, không thể truy cập từ bên ngoài:

(function square(n) {
	console.log(`Square of ${n} is ${n * n}`)
})
 
square(4) // Uncaught TypeError: square is not a function

Tuy nhiên, vẫn có thể truy cập được trong phạm vi của hàm, chẳng hạn như khi sử dụng đệ quy:

let factorial = (function calcFactorial(n){
	return n < 2 ? 1 : n * calcFactorial(n - 1)
})(3)
 
console.log(factorial) // 6
console.log(calcFactorial(3)) // Uncaught ReferenceError: calcFactorial is not defined

Encapsulation

Tận dụng private scope của self-invoking function, ta có thể tạo ra sự đóng gói dữ liệu (Encapsulation) của Object Oriented Programming.

Chẳng hạn ta có một đối tượng garage như sau:

const garage = {
	cars: [],
	
	add(car) {
		this.cars.push(car)
	},
	edit(index, car) {
		this.cars[index] = car
	},
	remove(index) {
		this.cars.splice(index, 1)
	}
}

Sử dụng self-invoking function để tạo ra một object có chứa các property và method như sau:

const garage = (function() {
	const cars = []
	
	return {
		getCar(id) {
			return cars[id]
		},
		add(car) {
			cars.push(car)
		},
		edit(index, car) {
			cars[index] = car
		},
		remove(index) {
			cars.splice(index, 1)
		}
	}
})()

Thuộc tính cars của đối tượng garage sẽ được ẩn giấu khỏi thế giới bên ngoài và chỉ có thể truy cập thông qua các phương thức đã định nghĩa.

Ví dụ:

garage.add({ name: 'BMW' })
garage.add({ name: 'Toyota' })
 
console.log(garage.cars) // undefined
console.log(garage.getCar(1)) // {name: "Toyota"}

Đây đồng thời cũng là một ứng dụng của Closure.

Summary

Điểm mạnh

  • Tính đóng gói, các thành phần bên trong self-invoking không thể được truy cập từ bên ngoài.
  • Cho phép xây dựng các factory function (là hàm tạo ra và trả về một object).

Điểm yếu

  • Không có tính gọi lại.
list
from [[Self-Invoking Functions]]
sort file.ctime asc

Footnotes

  1. xem thêm JS Functions.