Đối số hàm trong javaScrip

Có thể nói javaScript là ngôn ngữ hướng đối tượng, hầu như mọi thứ trong javaScript đều làm việc theo hướng đối tượng như chuỗi, mảng...
bên trong mọi hàm javaSctipt đều tồn tại một biến theo ngữ cảnh có tên là arguments, nó không phải là một mảng đúng nghĩa vì mảng trong javaScript cũng là một đối tượng, với arguments bạn không thể thay đổi nó hay bạn không thể dùng phương thức push() để thêm những phần tử mới, nhưng bạn lại có thể truy cập những phần tử như cách trong mảng, nó cũng có thuộc tính length như trong mảng. Hãy xem xét những vị dụ sau để rõ hơn điều này.

Ví dụ bạn sẽ tạo ra một hàm với 2 đối số sau đó bạn sẽ gọi hàm trong 2 trường hợp: một là gọi hàm đó với 1 tham số, hai là gọi hàm đó với 2 tham số và tham số thứ 2 là một đối tượng, ở đây bạn sẽ sử dụngarguments để kiểm tra xem hàm có bao nhiêu đối số:

function sendMessage( msg, obj ) {
// Nếu có 2 đối số
if ( arguments.length == 2 )
  // gọi phương thức của đối tượng là đối số thứ 2
  obj.handleMsg( msg );
else
  alert( msg );
}
// Gọi hàm với một tham số
sendMessage( "Hello, World!" );

// Gọi hàm với tham số thứ 2 là một đối tượng có phương thức handleMsg
sendMessage( "How are you?", {
   handleMsg: function( msg ) {
     alert( "This is a custom message: " + msg );
  }
});

Ví dụ thứ 2 rõ ràng hơn, chúng ta sẽ truy cập arguments và gán các phần tử của nó vào một mảng đúng nghĩa:

function makeArray() {
  var arr = [];
  // Lặp các phần tử trong arguments và đưa vào mảng arr
  for ( var i = 0; i < arguments.length; i++ ) {
    arr.push( arguments[i] );
  }
  return arr;
}
var myarr = makeArray('one','two');

// Kiểm tra mảng trả về từ hàm makeArray
for(var i=0; i<myarr.length; i++){
  document.write(myarr[i] +'<br />');
}

 

Hiểu rõ về các phương thức sự kiện được viết trong jQuery

DOM cho chúng ta một số event để tương tác với người dùng trên DOM, nay chúng ta có nhiều lựa chọn hơn khi dùng jQuery, khá nhiều phương thức được viết trong jQuery để thực hiện cho một vài sự kiện người dùng trên DOM hoặc một số sự kiện tự định nghĩa, qua nhiều phiên bản tiến lên có một số phương thức trong jQuery đã được thay thế nhưng vẫn giữ lại hoặc đã bỏ đi, một vài liệt kê sau sẽ sơ lược lại những gì jQuery đã viết để có cách nhìn rõ ràng và tổng quát hơn về các phương thức sự kiện cũng như có mối liên hệ tới các sự kiện trong DOM.

bind()/on()/delegate()/one()

  • bind( eventType [, eventData ], handler )
  • on( events [, selector ] [, data ], handler )
  • delegate( selector, eventType, eventData, handler )
  • one( events [, selector ] [, data ], handler )

từ phiên bản 1.7 thì jQuery đưa vào on() lúc này bind(), delegate() và on() đều như nhau vì thực tế chỉ là bind() và delegate() gọi on(), nhưng on() có thể gắn một hành động cho một hoặc nhiều sự kiện tới những phần tử được chọn, bind() thì không có thêm tham số selector (trong jQuery bind() gọi on() với tham số selector là null). Các sự kiện được liệt kê trong chuỗi bằng khoảng trắng để cùng thực hiện một hành động hoặc có thể chuyển vào dạng object để viết hành động cho từng sự kiện. Trong code jQuery phương thức one() cũng gọi on() nhưng với tham số cuối bằng 1 điều này để one() thực hiện hành động cho sự kiện chỉ lần đầu tiên điều này cũng tương đương với việc ta gọi off() trong khi thực hiện một hành động cho sự kiện bằng gọi on(). Tóm lại mục đích chung của tất cả các phương thức trên thì cũng tương tự như addEventListener() của DOM (từ IE8 về trước thì là attachEvent)

unbind()/off()/undelegate()

unbind() và undelegate() đều gọi off(), mục đích thì cũng tương tự như removeEvenListener() của DOM (từ IE8 về trước thì là detachEvent)

live(), die()

đã được remove từ phiên bản 1.9

blur()/focusout(), focus()/focusin()

blur() được giới thiệu trước và sau đó jQuery đã giới thiệu thêm focusout() đây là 2 phương thức ngắn gọn của on(), hai phương thức này tương tự nhau vì thực tế là blur() gọi focusout(), mục đích giống như phương thức onblur() và onfocusout() của DOM. Sự khác nhau của 2 phương thức này ở chỗ focusout() sẽ có thể ảnh hưởng lên các phần tử con của nó còn blur() phải là chính nó. Trong jQuery thì focusout() có thể làm việc được trên cả firefox còn onfocusout() của DOM hiện tại thì không (ví dụ http://jsfiddle.net/tuG33/1/). Cách hiểu về focus() và focusin() cũng tương tự.

load(), unload(), resize(), scroll(), submit(), change(), select(), click(), dblclick(), keydown(), keyup(), keypress()

Một phương thức ngắn gọn của on(), mục đích giống như onload(), onunload(), onresize(), onscroll(), onsubmit(), onchange(), onselect(), onclick(), ondblclick(), onkeydown(), onkeyup(), onkeypress() của DOM. Cần phải nói thêm về sự khác nhau giữa keydown/keyup với keypress là keydown/keyup thực hiện gán hành động cho sự kiện với các phím(key) trên bàn phím lúc này chúng ta có keycode còn keypress cũng gán hành động cho sự kiện với các phím nhưng lúc này là charcode nghĩa là ký tự được nhập vào nên nó sẽ không làm việc với các phím không nhập ký tự như shift, alt hay ctrl..., ví dụ phím "a" có keycode là 65 còn charcode là 97

hover() / mouseenter(), mouseleave() / mouseover(), mouseout()

  • hover( handlerIn, handlerOut )

Một phương thức ngắn gọn của on(). Đây đều là những sự kiện về mouse khi rê con trỏ lên và rời khỏi một phần tử nhưng sự khác nhau là mouseover() và mouseout() ảnh hưởng lên cả phần tử con của nó còn 2 cái kia thì chỉ chính nó. Những sự kiện này cũng tương tự như các sự kiện onmouseenter(), onmouseleave(), onmouseover(), onmouseout() của DOM. Sự kiện hover() là phương thức gọn để thực hiện 2 sự kiện là mouseenter() và mouseleave(), các phương thức sự kiện khác như mousemove(), mouseup(), mousedown() thì mục đích cũng tương tự như các sự kiện của DOM

error()

Dùng để thực hiện một hành động nào đó khi hình ảnh hoặc một phần tử nào đó tải không đúng, hiện phương thức này đã được phản đối từ phiên bản 1.8, phương thức này cũng gần tương tự như onerror() của DOM

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.