+5

Sơ lược lịch sử module javascript (Phần 1)

Nếu các bạn là người mới tập tành vào nghề phát triển website. Các bạn có bao giờ cảm thấy khó hiểu ngôn ngữ javascript này không, những thuật ngữ vô cùng khó hiểu như là: module, module loader, module bundler, CommonJS, AMD, Browserify, SystemJS, Webpack, JSPM... Hôm nay mình viết bài này, sẽ giải thích tường tận về chúng, đặc biệt bài viết này sẽ rất hữu ích cho người mới. Let's go!

Ví dụ về ứng dụng demo

Trong bài post này mình sẽ sử dụng ứng dụng đơn giản để giải thích những ngữ cảnh của module. Ứng dụng vô cùng đơn giản là tính tổng của các phần tử trong mảng. Mình đưa kịch bản là ứng dụng này sẽ có 4 function và 1 file index.html (để hiển thị kết quả thông qua thể span).

Function main sẽ tính tổng của các phần tử trong mảng, sẽ hiển thị kết quảspan#answer. Function main sẽ có 2 dependency là addreduce. Hàm add là callback của hàm reduce và tính tổng của 2 số, hàm reduce duyệt sẽ từng phần tử của array. Bạn sẽ dễ hình dung hơn khi xem 2 bức ảnh bên dưới.

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>JS Modules</title>
  </head>
  <body>
    <h1>
      The Answer is
      <span id="answer"></span>
    </h1>
  </body>
</html>
// 0-index.html
var values = [ 1, 2, 4, 5, 6, 7, 8, 9 ];
var answer = sum(values)
document.getElementById("answer").innerHTML = answer;
// main.js
function sum(arr){
  return reduce(arr, add);
}
// sum.js
function add(a, b) {
  return a + b;
}
// add.js
function reduce(arr, iteratee) {
  var index = 0,
    length = arr.length,
    memo = arr[index];
  for(index += 1; index < length; index += 1) {
    memo = iteratee(memo, arr[index]);
  }
  return memo;
}

Các bạn nên đọc kĩ kịch bản mình đưa ra rồi mới tiếp tục nhé 😁

Inline Script

Inline script là khi chúng ta viết javascript vào trong <script></script> tag. Tôi dám cá với các bạn là hầu hết các js devloper đều đã từng viết theo cách này. Cách viết này sẽ có 3 nhược điểm:

  • Lack of Code Reusability: Ví dụ, như chúng ta cần function add cho page khác, công việc của chúng ta là copy + paste. Thật không hay tí nào
  • Lack of Dependency Resolution: Nếu chúng ta muốn sử dụng để hàm sum, thì chúng ta phải đặt hàm reduceadd lên trước. Nếu như hàm sum chúng ta có nhiều hơn 2 dependency thì sao, chúng ta phải sắp xếp khéo léo để cho dependency load trước hàm sum.
  • Pollution of global namespace: làm ô nhiễm tên global trong javascript, nếu chúng ta đặt tên biến global không khéo sẽ trùng tên => nó sẽ sinh bug không đáng có.
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>JS Modules</title>
  </head>
  <body>
    <h1>
      The Answer is
      <span id="answer"></span>
    </h1>

    <script type="text/javascript">
      function add(a, b) {
        return a + b;
      }
      function reduce(arr, iteratee) {
        var index = 0,
          length = arr.length,
          memo = arr[index];
        for(index += 1; index < length; index += 1){
          memo = iteratee(memo, arr[index])
        }
        return memo;
      }
      function sum(arr){
        return reduce(arr, add);
      }
      /* Main Function */
      var values = [ 1, 2, 4, 5, 6, 7, 8, 9 ];
      var answer = sum(values)
      document.getElementById("answer").innerHTML = answer;
    </script>
  </body>
</html>

Script tags

Cách này có vẻ ổn hơn một chút, là chúng ta sẽ chia nhỏ các function add, reduce, sum, main ra từng file khác nhau, và khai báo trong <script src=”…”></script>. Với cách này code của chúng ta có thể tái sử dụng được. Tránh việc copy + paste vi phạm nguyên tắc DRY trong lập trình. Nhưng cách này cũng sẽ có một số khuyết điểm:

  • Lack of Dependency Resolution: Việc sắp xếp các file javascript rất quan trọng, ta phải sắp xếp của dependency trước add.js, reduce.js và sum.js rồi mới đến filemain.js. Sẽ rất khó quản lí khi dependency nhiều lên. File nào load trước file nào load sau cũng làm cho chúng ta đau đầu.
  • Pollution of global namespace: việc chúng ta khai báo như vậy, cũng ảnh hưởng đến global rồi.
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>JS Modules</title>
  </head>
  <body>
    <h1>
      The Answer is
      <span id="answer"></span>
    </h1>

    <script type="text/javascript" src="./add.js"></script>
    <script type="text/javascript" src="./reduce.js"></script>
    <script type="text/javascript" src="./sum.js"></script>
    <script type="text/javascript" src="./main.js"></script>
  </body>
</html>

Các bạn đễ ý nội dung của file add.js, reduce.js, sum.js, main.js mình đã giới thiệu ở phần mô tả nhé.

Module Object and IIFE(Module Pattern)

IIFE viết tắt Immediately-invoked function expression(IIFE) với cách viết module này sẽ giảm ô nhiễm không gian tên của global. Cách viết này sẽ khái báo 1 biến global là object. Và object này sẽ return về tất cả các methods và giá trị value cần thiết của ứng dụng.

// my-app.js
var myApp = {};
// add.js
(function(){
  myApp.add = function(a, b) {
    return a + b;
  }  
})();
// reduce.js
(function(){
  myApp.reduce = function(arr, iteratee) {
    var index = 0,
      length = arr.length,
      memo = arr[index];
  
    index += 1;
    for(; index < length; index += 1){
      memo = iteratee(memo, arr[index])
    }
    return memo;
  }  
})();
// summ.js
(function(){
  myApp.sum = function(arr){
    return myApp.reduce(arr, myUtil.add);
  }  
})();
// main.js
(function(app){
  var values = [ 1, 2, 4, 5, 6, 7, 8, 9 ];
  var answer = app.sum(values)
  document.getElementById("answer").innerHTML = answer;
})(myApp);
// index.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>JS Modules</title>
  </head>
  <body>
    <h1>
      The Answer is
      <span id="answer"></span>
    </h1>

    <script type="text/javascript" src="./my-app.js"></script>
    <script type="text/javascript" src="./add.js"></script>
    <script type="text/javascript" src="./reduce.js"></script>
    <script type="text/javascript" src="./sum.js"></script>
    <script type="text/javascript" src="./main.js"></script>
  </body>
</html>

Lưu ý khi viết theo chuẩn IIFE

(function(){ /*... your code goes here ...*/ })();

Khi khai báo trong function này, tất cả các biến được khai báo local variables với var ở đây này sẽ có phạm vi trong function này thôi, không ảnh hưởng gì đến global. Để ý một chút, để chúng ta gọi các function thông qua biến myApp được khai báo đầu tiên trong file my-app.js

myApp.add(1,2);
myApp.sum([1,2,3,4]);
myApp.reduce(add, value);

Ngoài cách trên ra, chúng ta có thể truyền param myApp object thông qua IIFE parameter. Có thể xem chi tiết hơn trong file sum.js. Viết cách viết này sẽ làm code của chúng ta thêm nguy hiểm hơn 😆

(function(obj){
  // obj is new veryLongNameOfGlobalObject
})(veryLongNameOfGloablObject);

Với cách viết này sẽ được cải tiến hơn 2 cách trên phải không nào? Các bạn có thấy cách viết này giống với thư viện Jquery mà hay dùng không. Biến global $ object ( hoặc jQuery), chúng ta có thể gọi tất cả các function thông qua $này ví dụ như $.map, $.get, $.post, $.ajax, $.each ... Đây không phải là cách tồi để viết module. Nhưng nó vẫn còn 2 khuyết điểm

  • Lack of Dependency Resolution: cũng giống với cách 2, chúng ta phải sắp xếp các dependency trước rồi mới load file main.js. Việc này chúng ta phải sắp xếp bằng tay thôi, ứng dụng khi phình to sẽ rất cực khi quản lí chúng đấy.
  • Pollution of global namespace: Tuy cách này đã tránhh việc khai báo global, ứng dụng lớn lên thì việc quản lí đặt tên cũng làm cho chúng ta nhức nhối.

Bài viết này mình sẽ chia ra 2 phần để cho mọi người dễ đọc, dễ nắm bắt được nội dung hơn. Một câu hỏi đặt ra có cách viết module nào mà giải quyết được 3 khuyết điểm trên không, xin thưa là có đấy. Đó là chuẩn module CommonJS được nodejs lấy làm chuẩn viết module. Nếu server đã có chuẩn CommonJS thì ở client cũng có chuẩn module AMD load bất đồng bộ, thật tuyệt vời 😂. UMD  sẽ bao gồm CommonJSAMD dùng cho cả client lẫn server :face_with_hand_over_mouth: . Với sự xuất hiện của chuẩn module mà ES6 đã giới thiệu import, export. Nói đến đây nhiều chuẩn module quá hì =). Đành phải chịu thôi, chuẩn module ES6 ra đời quá lâu trong khi phía front-end đã ngày càng phức tạp nên anh em js developer họp lại đưa ra chuẩn module chung nhất nổi tiếng nhất là AMDCommonJS. Chuẩn module nào sẽ thống trị trong thế giới JS đây. Đó là câu trả lời vô cùng nan giải ở thời điểm hiện tại. Mình tin chuẩn ES6 sẽ trở nên phổ biến hơn, quan trọng vấn đề là thời gian thôi. Đây là bản tóm tắt sơ lược về module js, mình sẽ viết ở phẩn sau nhé.Cảm ơn các bạn đã quan tâm bài viết này :clap_tone1: :clap_tone1:


All Rights Reserved

Viblo
Let's register a Viblo Account to get more interesting posts.