Module Pattern trong Javascript

Việc giới hạn phạm vi ảnh hưởng và khả năng bị ảnh hưởng của các property và các biến và một việc cực kỳ quan trọng thế nhưng, Javascript lại không phải là một ngôn ngữ OOP và nó cũng không có cơ chế riêng cho phép chúng ta tạo ra các private property cho instance của các constructor function. Tuy nhiên, bằng việc áp dụng module pattern, chúng ta hoàn toàn có thể làm được điều này một cách dễ dàng.

Khời tạo một Module

Module cho phép chúng đóng gói các property, method, biến và function bằng cách tận dụng một đặc điểm rất đặc trưng của Javascript đó là closure. Các closure sẽ được tạo ra mỗi khi một function bất kỳ được tạo ra và đây cũng chính là cách mà ta tạo ra một module, đó là tạo ra một IIFE(Immediately Invoked Function Expression), một function tự gọi đến chính nó.

var testModule = (function() {
  var counter = 0; // biến này đã được cô lập bởi closure của function này
  
  ...
})();

Bằng cách khởi tạo biến counter bên trong một IIFE chúng ta đã giới hạn phạm vi bị tác động của counter giờ đây, ngoài các đoạn code bên trong IIFE này ra thì các đoạn code bên ngoài không thể truy cập cũng như sử dụng được biến counter. Ngoài các biến thì chúng ta cũng có thể khởi tạo các private function bằng cách này.

Export các method và property

Chúng ta đã biết cách tạo ra các biến private, nhưng để có thể sử dụng được các module thì chúng ta cần export các property cũng như các method hoặc đơn giản chỉ là các biến hoặc function ra bên ngoài.

var testModule = (function() {
  var counter = 0;
  
  return {
      getCounter() {
          return counter;
      },
      increateCounter() {
          counter++;
      },
      resetCounter() {
          counter = 0;
      }
  }
})();

for (var i = 0; i < 10; i++) {
  testModule.increateCounter();
}
console.log(testModule.getCounter()); // 10

testModule.resetCounter();
console.log(testModule.getCounter()); // 0

console.log(testModule.counter); // undefined

Như các bạn có thể thấy, biến counter không thể bị truy cập từ bên ngoài module testModule, nhưng chúng ta vẫn có thể cho phép các developer khác thực hiện các tác vụ liên quan đến counter thông qua các method mà ta cho export ra cho họ sử dụng.

Global import

Khi phân tích kỹ cú pháp của IIFE thì các bạn có thể thấy tất cả những gì mà ta làm chỉ là truyền một function expression vào một cặp dấu () để tránh Javascript hiểu lầm cặp dấu{} của function expression mà ta truyền vào () là cú pháp khời tạo Object hoặc chúng là cặp dấu {} của các statement như if, switch,... và sau đó ta sẽ dùng thêm một cặp dấu () nữa để chạy function expression mình vừa đề cập ở trên.

Tất cả những gì chúng ta tạo ra 1 IIFE chỉ là chạy một function expression. Ngoài việc giúp cho code của chúng ta ngắn hơn và ta không phải tạo thêm một biến để chứa function expression đó thì nó không khác gì lắm với ví dụ dưới đây cả.

var functionExpression = function() {}

functionExpression();

Chính vì bản chất đơn giản của IIFE mà ta hoàn toàn có thể truyền một biến vào trong IIFE tương tự như khi ta truyền một biến vào trong một function.

var globalVar = 'test';

var testModule = (function(global) {
  console.log(global);
})(globalVar);

Trong Module pattern thì việc truyền một biến bên ngoài vào 1 module được gọi là global import.

Với các module bị phụ thuộc vào các thư module khác và các module khác này lại nằm ở các file khác nhau thì các bạn có thể áp dụng gobal import như ví dụ dưới đây chẳng hạn.

var testModule = (function($) {
  const bodyEle = $('body');
  ...
})($); // truyền function $ của thư viện jquery vào module testModule

Lời kết

Như các bạn có thể thấy Module mang lại rất nhiều lợi ích ngoài việc định nghĩa các biến và function một cách private thì nó cũng giúp cho việc tái sử dụng trở nên dễ dàng hơn và giảm thiểu xung đột về tên biến đi rất nhiều. Mong rằng qua bài viết này các bạn đã hiểu về pattern này. Chúc các bạn một ngày làm việc vui vẻ và hiệu quả. Cheer !


All Rights Reserved