Những mẫu thiết kế mã javaScript: Constructor, Object Literal, Module

Có nhiều cách thức viết mã javaScript khác nhau kể cả có sử dụng thư viện hoặc không sử dụng thư viện hổ trợ. Trong javaScript chúng ta cũng có nhiều cách để khai báo và sử dụng đối tượng. Chúng ta cũng biết các thư viện hay các ứng dụng javaScript cũng được viết theo một mẫu nào đó. Việc viết mã theo những mẫu hướng đối tượng là điều quan trọng, cũng như những ưu điểm của lập trình hướng đối tượng bởi sự chặt chẽ logic, sử dụng giễ dàng, giễ kiểm soát khi ứng dụng lớn, có khả năng tái sử dụng và phát triển tiếp... Chúng ta sẽ tìm hiểu một số mẫu javaScript đang được sử dụng cả những mẫu cổ điển và hiện đại trong bài viết này.

Mẫu Constructor

Như chúng ta đã biết javaScript không hổ trợ khái niệm lớp nhưng nó lại hỗ trợ việc hàm cũng có cách làm việc như đối tượng. Với việc gọi hàm với từ khóa "new" chúng ta đã tạo ra đối tượng từ việc gọi hàm và có thể sử dụng các thành phần trong hàm như hướng đối tượng, ở mẫu constructor từ khóa this sử dụng xuyên suốt trong hàm lúc này được hiểu như việc tham chiếu chính là đối tượng được tạo. Ví dụ:

function Kconstruct(a, b){
   this.propertya = a;
   this.propertyb = b;
   this.methodC = function(){
      return: this.propertya + " and " + this.propertyb;
   }
}
var myk = new Kconstruct("you", "me");
// Kiểm tra
console.log(myk.methodC()); // "you and me"

Chúng ta còn có thể hiểu phương thức methodC được viết như trên còn được gọi là phương thức privileged, khi sử dụng mẫu constructor với prototype thì còn được hiểu như phương thức public như bài viết trước (hướng đối tượng trong javaScript) đã bàn đến

Mẫu Object Literal

Đây là mẫu phổ biến bởi khả năng đóng gói và độ tổ chức mã cao, các chức năng được phân tách rạch ròi, nó cũng giữ cho mã của bạn có được sự rõ ràng và làm hạn chế được việc đụng độ bởi việc sử dụng không gian tên.

var myObjectLiteral = {
    variableKey: variableValue,
    functionKey: function () {
      // ...
    }
};

Nhìn có vẻ giống JSON nhưng bạn nên chú ý đây không phải là cú pháp chuẩn của JSON nên nó không phải là JSON. Việc sử dụng mẫu này các chức năng được phân tách nhiều nên đồng thời lượng mã viết cũng nhiều dòng hơn và các chức năng được liên kết với nhau khá nhiều nên phần nào cũng gây khó khăn cho việc đọc hiểu ứng dụng với những người chưa có kinh nghiệm sử dụng mẫu, và phải định hình các chức năng được phân tách ngay từ đầu nên cũng khó khăn cho người mới viết mã nhưng nó cũng là ưu điểm. Để hiểu thêm về mẫu object literal bạn có thể đọc một bài viết trên rmurphey.com tại đây sẽ có 2 vị dụ rất tốt để bạn hiểu về object literal.

Mẫu Module

Có thể nói mẫu Module có cở sở dựa trên object literals, là một phần của object literals mẫu Module sử dụng object literals chỉ có điều là nó được trả về trong phạm vi của hàm. Trước tiên chúng ta sẽ cùng xem ví dụ:

var myNamespace = (function () {
  var myPrivateVar, myPrivateMethod;
  // A private counter variable
  myPrivateVar = 0;
  // A private function which logs any arguments
  myPrivateMethod = function( foo ) {
      console.log( foo );
  };
  return {
    // A public variable
    myPublicVar: "foo",
    // A public function utilizing privates
    myPublicFunction: function( bar ) {
      // Increment our private counter
      myPrivateVar++;
      // Call our private method using bar
      myPrivateMethod( bar );
    }
  };
})();

Xem ví dụ trên ta có thể hiểu ngay rằng mẫu Module này giúp có được cả những thuộc tính và phương thức ở dạng public và private, điều này phân định rạch ròi những thứ chỉ được truy cập bên trong và những thứ có thể truy cập ở ngoài, bạn không thể chạm tới những thứ private mà phải thông qua những thứ public bởi những thứ public này có thể chạm tới những thứ private đó là 1 đặc điểm quan trọng của mẫu Module. Ở trên bạn cần chú ý tới từ khóa var khi khai báo biến để phạm vi của nó ở trong hàm. Thay vì tạo đối tượng khi return chúng ta còn có cách viết khác như sau:

// Global module
var myModule = (function () {
  var privateVariable = "Hello World";
  function privateMethod() {
    // ...
  }
  // Module object
  var module = {};
  module.publicProperty = "Foobar";
  module.publicMethod = function () {
    console.log( privateVariable );
  };
  return module;
}());

cách viết trên có ưu điểm là chúng ta có thể tạo ra nhiều đối tượng Object Literal có thể tương tác lẫn nhau trong global module và chỉ đối tượng nào được trả về thì mới là public, người ta gọi cái này là module export.

Nếu muốn sử dụng các đối tượng Global như thư viện jQuery chẳng hạn chúng ta chỉ việc truyền vào tham số hàm nặc danh của module, lúc này có thể đặt định danh cho nó như bạn muốn, người ta gọi cái này là global import. Ví dụ:

(function (JQ, YH) {
// Lúc này có thể truy cập jQuery như JQ và Yahoo như YH
}(jQuery, YAHOO));

Với mẫu module thì nó được viết xuyên suốt trong một file, trường hợp người khác hoặc chính bạn muốn viết tiếp hay mở rộng cho module mà không làm ảnh hưởng đến file ban đầu, nói cách khác là chúng ta muốn viết module ở nhiều file khác nhau thì cách thức thực hiện như ví dụ sau:

var myModule = (function (my) {
   // Thêm một phương thức
   my.anotherMethod = function () {
      // Ví dụ gọi phương thức publicMethod() đã viết
   my.publicMethod();
   };
   return my;
}(myModule || {}));

Ở ví dụ trên đoạn (myModule || {}) nói rằng nếu myModule chưa được tạo thì nó sẽ tạo ra module mới, bạn cần chú ý tham số my trong hàm nặc danh lúc này được trả về có ý nghĩa đại diện cho đối tượng module, người ta gọi cách viết trên là loose augmentation. Trường hợp bạn tạo lại thuộc tính hoặc phương thức đã có nó sẽ bị override, lúc này để đảm bảo vẫn có thể thao tác được với những thứ đã viết ta cần lưu vào thuộc tính hay biến mới.

Tiếp theo sẽ là trường hợp tạo ra một module mới có thể kế thừa lại những thuộc tính và phương thức public của một module đã tạo, cũng giống như cách trên nhưng có điều tham số trong hàm nặc danh của module lúc này đại diện cho module đã tạo mà bạn muốn kế thừa và giá trị trả về lúc này là đối tượng của module mới. Ví dụ:

var myModule_Two = (function (old) {
   var my = {}, key;
      for (key in old) {
         if (old.hasOwnProperty(key)) {
            my[key] = old[key];
         }
      }
      // Tạo một phương thức
      my.moduleMethod = function () {
         // Gọi lại phương thức đã sao chép
         my.publicMethod(); // có thể gọi old.publicMethod() nếu chưa sao chép
};
return my;
}(myModule));

Vòng for ở ví dụ trên đã sao chép tất cả thuốc tính và phương thức của module myModule đã tạo thông qua tham số đối tượng là old, lúc này trong vòng for sẽ dùng phương thức hasOwnProperty() trong javaScript để kiểm tra xem đối tượng old có tồn tại thuộc tính đó không nếu có thì làm giá trị cho đối tượng my, hàm nặc danh lúc này sẽ trả về đối tượng my cho module mới. Bạn có thể tìm hiểu kỹ hơn về mẫu module tại một bài viết ở adequatelygood.com

Những mẫu thiết kế mã javaScript: Singleton pattern

Mẫu Singleton được biết đến nhiều trong các ngôn ngữ lập trình hướng đối tượng, nếu bạn đã có kinh nghiệm ở một ngôn ngữ lập trình hướng đối tượng phổ biến nào đó chắc sẽ không còn xa lạ, chúng ta sẽ thấy một số framework họ luôn muốn dùng một phương thức để dùng khởi tạo đối tượng như getInstance(). Cũng như các ngôn ngữ khác mục đích chính của mẫu này là nhắm tránh việc chạy đi chạy lại một thứ gì đó khi khởi tạo đối tượng nhiều lần. Bằng việc sử dụng 1 phương thức để khởi tạo thường được đặt tên là getInstance(). Ví dụ:

var mySingleton = (function () {
  var instance;
  function init() {
    // Private
    var privateRandomNumber = Math.random();
    return {
       // Public
       getRandomNumber: function() {
          return privateRandomNumber;
       }
    };
  }
  return {
    getInstance: function () {
       if ( !instance ) instance = init();
       return instance;
    }
  };
})();
var singleA = mySingleton.getInstance();
var singleB = mySingleton.getInstance();
console.log(singleA.getRandomNumber());
console.log(singleB.getRandomNumber());

Kết quả ở trên cho thấy khi bạn khởi tạo đối tượng bằng phương thức getInstance() thì hàm init() chỉ chạy lần đầu những lần sau biến instance đã lưu trữ thông tin nên chỉ việc trả về, nếu chúng ta bỏ câu điều kiện if(!instance) thì kết quả sẽ khác. Điều này là có ích khi bạn muốn những chức năng cài đặt được chạy duy nhất lần đầu, thực hiện được điều này ta đã góp phần tối ưu được ứng dụng, nhưng nó cần được thực hiện ở một mục đích cụ thể khi cần thiết nhằm tránh lạm dụng. Với cách viết trên thì chúng ta cũng bắt buộc phải khởi tạo đối tượng bằng phương thức getInstance() bạn không được gọi trực tiếp chẳng hạn như new MySingleton(). Mẫu Singletons còn được sử dụng trong constructor, cũng với cách hiểu như trên chúng ta cũng sử dụng một biến để lưu thông tin đối tượng tạm gọi là instance. Ví dụ:

function Singleton() {
   if (typeof Singleton.instance === 'object') {
      return Singleton.instance;
   }
   var randomNumber = Math.random();
   this.testNumber = function(){
      console.log(randomNumber);
   };
   // cache
   Singleton.instance = this;
}
var user = new Singleton();
var person = new Singleton();
user.testNumber();
person.testNumber();

Kết quả ví dụ trên khẳng định rằng các biến instance đã có tác dụng trong việc lưu thông tin đối tượng và những lần khởi tạo đối tượng tiếp theo nó chỉ trả về. Chúng ta có thể kiểm tra biến instance phía cuối bằng cách gom những thứ cần thực hiện trong một hàm nhưng việc kiểm tra ngay ở phía trên là tối ưu hơn. Trong constructor chúng ta cũng có thể trả về một đối tượng, điều này có nghĩa những thông tin trong đối tượng đó sẽ có tính chất là public còn những thứ còn lại trong constructor sẽ có tính chất là private (sử dụng từ khóa var để có phạm vi trong hàm), lúc này để áp dụng tinh thần của mẫu Singleton chúng ta sẽ kiểm tra đối tượng để quyết định trả về luôn cho những lần gọi tiếp theo. Ví dụ:

function Singleton(){
  if(Singleton.instance !== undefined)
     return Singleton.instance;
  var privateMumber = Math.random();
  // All public methods
  Singleton.instance = {
     getMumber: function(){
        return privateMumber;
     }
  };
  return Singleton.instance;
}
s = new Singleton();
s2 = new Singleton();
console.log(s.getMumber());
console.log(s2.getMumber());

Chúng ta có thể thấy mẫu có Singleton cũng có nhiều cách thức viết khác nhau nhưng ý nghĩa chung thì không thay đổi, tùy theo cách sử dụng đối tượng trong javaScript của bạn và mục đích của bạn để quyết định có nên dùng mẫu Singleton hay không và dùng như thế nào. Nếu sử dụng tốt ứng dụng của bạn sẽ khá tối ưu.

Kế thừa từ prototype trong javaScript

Bài viết trước đã trình bày về những kỹ thuật căn bản để xây dựng ứng dụng javaScript theo hướng đối tượng, cách tạo và sử dụng thuộc tính, phương thức của đối tượng. Để có thể xây dựng code javaScript có thể dùng lại bài này sẽ giới thiệu về kế thừa trong javaScript thông qua đối tượng prototype.

Khi bạn tạo phương thức hay thuộc tính public cho đối tượng thông qua đối tượng prototype, bạn có thể kế thừa lại nó thông qua một đối tượng khác. Ví dụ sau sẽ trình bày về kỹ thuật này:

// Create the constructor for a Person object
// Tạo cấu trúc cho đối tượng Person
function Person(nam​e, password) {
  this.name = name;
  this.password = password;
}

// Thêm phương thức mới cho đối tượng Person
Person.prototype.getName = function () {
  return this.name;
};
Person.prototype.getPassword = function () {
  return this.password;
};
Person.prototype.getLevel = function () {
  return this.level;
};

// Thêm thuộc tính cho đối tượng Person
Person.prototype.level = 'admin';

// Tạo cấu trúc cho đối tượng User
function User(name) {
  // Tạo thuộc tính cho User
  this.name = name;
};

// Đối tượng User thừa kế tất cả các phương thức,
// thuộc tính của đối tượng Person được tạo thông qua prototype
User.prototype = new Person();

// Tạo đối tượng
var person = new Person('Ti', '789');
var user = new User('Teo', '123456');

alert(user.getName()); // Teo
alert(user.getLevel()); // admin
alert(user.getPassword()); // Chưa định nghĩa(undefined)
alert(person.getName()); // Ti

Qua ví dụ trên bạn có thể thấy được đối tượng User đã kế thừa lại 3 phương thức của đối tượng Personđược tạo thông qua prototype và nó cũng kế thừa luôn thuộc tính level của đối tượng Person. Đối tượngUser kế thừa phương thức getName() từ đối tượng Person nhưng kết quả trả về là thuộc tính name của đối tượng User từ đây bạn chú ý một điều là nó không kế thừa lại thuộc tính được tạo bên trong đối tượngPerson, mặc dù đối tượng User đã kế thừa phương thức getPassword() từ đối tượng Person nhưng trong phương thức getPassword() có trả về thuộc tính password mà nó chưa được định nghĩa ở đối tượng Usernên bạn sẽ nhận được kết quả undefined.

Hướng đối tượng trong javaScript

Không giống như hầu hết các ngôn ngữ hướng đối tượng khác, trên thực tế javaScript không thực sự có khái niệm lớp, trong hầu hết các ngôn ngữ lập trình bạn phải xây dựng lớp sau đó mới khai báo đối tượng để sử dụng. Trong javaScript đối tượng được tạo trực tiếp và có thể tạo bằng nhiều cách , đối tượng cũng có thể kế thừa từ đối tượng khác. Để tạo đối tượng bạn có hai cách cơ bản để tạo đối tượng như sau:

// Tạo đối tượng thông qua cú pháp new Object()
var obj = new Object();
// Tạo một số thuộc tính cho đối tượng
obj.val = 5;
obj.click = function () {
  alert("hello");
};

// Sử dụng cách viết ngắn gọn bằng cách dùng dấu {...}
var obj = {
  val: 5,
  click: function () {
    alert("hello");
  }
};

Trong thực tế khi bạn khai báo một hàm bất kỳ trong javaScript cũng có thể được mô tả cho một đối tượng, trong thực tế điều này có thể gây nhiều khó hiểu. Chúng ta sẽ cùng xem xét ví dụ sau để hiểu rõ hơn điều này:

// Tạo một hàm đơn giản, bên trong có tạo một thuộc tính name
function User(x) {
  this.name = x;
}
// Khai báo đối tượng từ hàm trên
var me = new User("My Name");
// Kiểm tra thuộc tính
alert(me.name);

// Dùng phương thức construtor để kiểm tra đối tượng User
alert(me.constructor == User);
// Chạy hàm User
User("Test");

// Trong bối cảnh hàm trên từ khóa 'this' không được xét,
// mặc định nó có thể được gán cho đối tượng toàn cầu 'window',
// nghĩa là bạn có thể truy cập tới thuộc tính name
alert(window.name);

Phương thức public

Phương thức public giúp bạn sử dụng nó bên ngoài đối tượng, bạn có thể tạo phương thức public bên ngoài đối tượng thông qua một đối tượng trung gian đó là prototype. Ví dụ:

// Tạo cấu trúc đối tượng User
function User(name, age) {
  this.ten = name;
  this.tuoi = age;
}
// Thêm phương thức getName cho đối tượng User
User.prototype.getName = function () {
  return this.ten;
};
// Thêm phương thức getAge cho đối tượng User
User.prototype.getAge = function () {
  return this.tuoi;
};

// Tạo một biến user lưu đối tượng User
var user = new User("Bob", 44);
// Kiểm tra hai phương thức của đối tượng
alert(user.getName());
alert(user.getAge());

Bạn có thể thấy prototype thực sự tiện lợi giúp bạn tạo phương thức của đối tượng một cách giễ ràng, và nó được dùng phổ biến để xây dựng ứng dụng javaScript

Phương thức private

Phương thức private được tạo ra bên trong đối tượng điều này làm cho bạn không thể truy cập nó bên ngoài đối tượng, nó tạo sự chặt chẽ cho ứng dụng của bạn, khỏi đụng độ với các đối tượng khác khi bạn phát triển ứng dụng với lượng code lớn. Xem ví dụ sau để hiểu hơn về phương thức private:

// Tạo cấu trúc cho đối tượng Classroom
function Classroom(students, teacher) {
  // Tạo một phương thức private hiển thị sinh viên trong lớp
  function get() {
    alert(students.join(", "));
  }
  // Tạo thuộc tính đối tượng lưu trữ thông tin
  this.students = students;
  this.teacher = teacher;
  // Gọi phương thức get
  get();
}

// tạo đối tượng Classroom
var myclass = new Classroom(["Chiến", "Đinh"], "Thảo");
// Kiểm tra thuộc tính teacher
alert(myclass.teacher)
// Gọi phương thức private bên ngoài đối tượng
myclass.get();

Kết quả ví dụ trên khi gọi phương thức private bên ngoài đối tượng ta nhận được một thông báo lỗi là đối tượng Classroom không có phương thức get()

phương thức privileged

Đây là phương thức được tạo bên trong đối tượng và bạn có thể truy cập nó bên ngoài đối tượng, thực ra nó là một hàm "nặc danh" được lưu vào một thuộc tính trong đối tượng. Xem ví dụ sau để hiểu rõ hơn điều này:

// Tạo cấu trúc đối tượng User
function User(name, age) {
  // Tạo biến lưu thông tin năm sinh
  var year = (new Date()).getFullYear() - age;
  // Tạo phương thức Privileged trả về năm sinh
  this.getYearBorn = function () {
    return year;​
  }
}

// Tạo đối tượng User
var user = new User("Bob", 44);
// Kiểm tra phương thức getYearBorn
alert(user.getYearBorn());

Phương thức static

Trước kia khi tôi tìm hiểu về kỹ thuật tạo phương thức static, tôi đã bị bất ngờ bởi kết quả mà nó đem lại. Trước tiên chúng sẽ ta cùng xét một số trường hợp sử dụng đối tượng trong javaScript với phương thức public. Bạn  sẽ sử dụng lại ví dụ đã trình bày ở phần phương thức public.

Nếu bạn gán đối tượng user cho một biến user2 và kiểm tra các phương thức và thuộc tính của user trên user2 thì thực chất user và user2 là một, cùng là đối tượng User vì mọi thay đổi trên user hay user2 đều ảnh hưởng lẫn nhau. Ví dụ:

var user = new User('Tí',100);
var user2 = user;
alert(user2.getName()); // Kết quả: Tí

// Thay đổi thuộc tính 'ten' trên user2
user2.ten = 'Tẻo';
alert(user2.getName()); // Kết quả: Tèo
alert(user.getName()); // Kết quả: Tèo

// Nếu tạo thêm thuộc tính cho đối tượng user2
user2.interest = 'web';
alert(user.interest); // Kết quả: web

Nếu bạn muốn tạo ra đối tượng User khác thì bạn phải gọi lại và không thể thừa kế được giá trị gì từ đối tượng trước đã tạo, bạn buộc phại gọi lại đối tượng với tham số hay các giá trị cần thiết. Ví dụ:

var user = new User('Tí',100);
var user3 = new User('Tí',100);
alert(user3.getName()); // Tí

// Thay đổi thuộc tính 'ten' trên user3
user3.ten = 'Tèo';
alert(user3.getName()); // Tèo
alert(user.getName()); // Tí

// Nếu tạo thêm thuộc tính cho đối tượng user3
user3.interest = 'web';
alert(user.interest); // Kết quả: thuộc tính không xác định
alert(user3.interest); // Kết quả: web

Tuy khi thêm hay thay đổi user3 thì không ảnh hưởng gì đến user nhưng bạn không thừa kế được những giá trị đã được tạo ra hay thay đổi từ user mà bạn phải khai báo lại nó. Vấn đề này nảy sinh ra một kỹ thuật trong javaScript mà người ta gọi nó là phương thức static. Xem ví dụ sau để hiểu rõ điều này:

// Tạo phương thức static cloneUser tới đối tượng User
User.cloneUser = function (user) {
  // Tạo và trả về một đối tượng user mới
  return new User(
  // Nhân bản những phương thức hay thuộc tính đối tượng user
  user.getName(),
  user.getAge());
};

var user = new User('Tí', 100);
// Sử dụng phương thức cloneUser nhân bản đối tượng user
var other = User.cloneUser(user);
alert(other.getName()); // Tí

// Thay đổi giá trị thuộc tính 'ten' trên other
// đối tượng user không đổi
other.ten = 'Tèo';
alert(other.getName()); // Tèo
alert(user.getName()); // Tí

// Thay đổi giá trị thuộc tính 'ten' trên user.
// đối tượng other không đổi
user.ten = 'Tùng';
alert(user.getName()); // Tùng
alert(other.getName()); // tèo

// Nhân bản đối tượng user một lần nữa
var other1 = User.cloneUser(user);
alert(other1.getName()); // Tùng

// Nhân bản đối tượng other
var other2 = User.cloneUser(other);
alert(other2.getName()); // Tèo

Điều này thực sự hữu dụng nếu bạn phát triển ứng dụng lớn nhưng cũng có thể giễ gây nhầm lẫn nếu bạn mới làm quen với kỹ thuật này. Khi thực hiện một dự án nào đó thì nên nghĩ ngay tới việc làm sao đoạn mã của bạn có thể dùng lại giễ dàng và có thể phát triển tiếp, nếu mỗi lần dùng lại bạn lại phải tìm hiểu rồi sửa nhiều chỗ mới dùng được hay thậm chí phải thực hiện lại từ đầu thì quả là phí công sức nếu ứng dụng bạn có một lượng mã lớn, đó là vì sao việc viết mã theo hướng đối tượng là điều nên làm bởi vì ưu điểm của nó thì có lẽ bạn cũng biết, nhưng cũng có thể là không cần thiết với ứng dụng nhỏ và đơn giản, tất cả là tùy ở bạn. Tôi mong nhận được sự góp ý của tất cả các bạn có cùng chung sở thích.

 

Nguồn: http://sothichweb.com/article.php?aid=huong-doi-tuong-trong-javascript_0c92315

3 dấu bằng (===) trong Javscript

3 dấu bằng được dùng để so sánh giá trị lẫn kiểu dữ liệu trong Javascript,

<html>
<head></head>
<body>
    <script type="text/javascript">
        var a=1; // Numeric Value
        var b=1; // Numeric Value
        var c="1"; // String Value
        var d="1"; //String Value
        alert(a===b); // Alerts 'true' as both variables are numeric,having value 1
        alert(c===d); // Alerts 'true' as both variables are string,having value 1
        alert(b===c); // Alerts 'false'as both variables are different type,having value 1
    </script>
</body>
</html>