Custom Constructor Functions

Custom Constructor Functions

Ngoài việc khởi tạo một object trong javascript bằng object literal (var x = {}) pattern và các hàm tạo có sẵn (var x = new Object(), var y = new String(“name”) ), thì bạn có thể tạo ra một object bằng hàm khởi tạo tuỳ chỉnh của riêng bạn, Ví dụ sau là minh chứng :

var jonathan = new Person('Jonathan')
jonathan.say() // I am Jonathan

Cách khởi tạo đối tượng này rất giống với việc tạo ra một đối tượng trong Java bằng cách sử dụng một lớp là Person. Cú pháp tương tự nhưng thực sự thì trong Javascript không có lớp và Person chỉ là một hàm.

Đây là cách hàm tạo Person được định nghĩa.

var Person = function(name) {
  this.name = name;
  this.say = function() {
    return "I am " + this.name;
  }
}

Mỗi khi bạn gọi hàm khởi tạo với từ khoá new, vậy thì điều gì đã xảy ra: 

  • Một object rỗng được tạo ra và được tham chiếu bởi biến this, kế thừa các prototype của hàm đó.
  • Thuộc tính(Properties) và phương thức(Method) sẽ được thêm vào đối tượng this này.
  • Đối tượng this sẽ được ngầm định trả về ở cuối hàm.

Quá trình trên có thể trông tương tự như sau:

var Person = function(name) {
   var this = {}; // khởi tạo một đối tượng sử dụng object literal
   this.name = name; // thêm thuộc tính cho đối tượng
   this.say() = function() {
     return "I am " + this.name;
  };
   return this;
}

Phương thức say() trong hàm trên được thêm vào this. Vì vậy bất cứ khi nào ta gọi new Person() thì một hàm say() mới được tạo ta trong memory điều này là không cần thiết vì phương thức say() không thay đổi giữa các đối tượng Person được tạo ra.

Nên để tốt hơn ta sẽ chuyển những phương thức dùng chung này vào prototype :

Person.prototype.say = function () {
 return "I am " + this.name;
};

Chúng ta hay nhớ những thành viên có thể tái sử dụng (reusable members), như phương thức thì nên chuyển vào prototype.

Trong ví dụ trên chúng ta có :

var this = {};

Đối tượng rỗng không thực sự là rỗng bởi vì nó đã có được những phương thức kế thừa từ Person prototype. Vì vậy chúng ta có thể làm được những điều tương tự như: 

var Person = funciton(name) {
   this.name = name;
};
Person.prototype.say = funtion() {
   return "I am " + this.name;
};
var jonathan = new Person('Jonathan');
var oliver = new Person('Oliver');
jonathan.say(); // I am Jonathan
oliver.say(); // I am Oliver

Constructor’s Return Values

Khi được gọi với từ khoá new, một hàm khởi tạo sẽ luôn trả về một object, mặc định sẽ là đối tượng được tham chiếu bởi this. Nếu bạn không thêm bất cứ thuộc tính nào vào trong nó thì một đối tượng rỗng được trả về. Đối tượng this luôn luôn được trả về kể cả bạn không return nó trong hàm tạo. Tuy nhiên bạn có thể trả về bất kỳ object nào mà bạn muốn như ví dụ sau:

var Person = function(name) {
 this.name = name;
 var that = {};
 that.name = name + 'lo';
 return that;
}
var mi = Person('Mi');
console.log(mi.name) // Milo

Như ví dụ trên cho thấy bạn có thể return bất cứ gì bạn muốn miễn nó là đối tượng. Nếu bạn cố tình return thứ gì mà không phải đối tượng (string hay number, true, false) đều không gây ra lỗi nhưng chúng sẽ bị bỏ qua và đối tượng this sẽ được trả về thay thế.

Patterns for Enforcing new

Như các ví dụ ở trên, thì hàm tạo vẫn chỉ là các hàm nhưng được gọi với new. Như vậy thì điều gì sẽ xảy ra khi ta quên new khi gọi đến các hàm khởi tạo. Việc này sẽ không gây ra lỗi syntax hay lỗi trong quá trình runtime nhưng có thể dẫn đến lỗi logic trong code và kết quả không mong muốn. Đó là bởi vì khi bạn quên new thì this bên trong hàm tạo sẽ trỏ đến đối tượng toàn cục (object global) (Ở các trình duyệt thì sẽ trỏ đến windown )

var Waffle = function() {
  this.tastes = "yummy";
}

// create a new object with `new`
var good_morning = new Waffle();
console.log(typeof good_morning); // "object"
console.log(good_morning.tastes); // "yummy"

// forgotten `new`
var good_morning = Waffle();
console.log(typeof good_morning); // "undefined"
console.log(window.tastes); // "yummy"

Kết quả không mong muốn này đã được giải quyết ES5. Nhưng để không bị phụ thuộc thì bạn nên đảm bảo rằng hàm khởi tạo của bạn sẽ hoạt động như môt hàm bình thường kể cả khi không được gọi với new. Ví dụ :

Cách đơn giản nhất là quy ước đặt tên. Bạn nên viết in hoa chữa cái đầu tiên (Waffle) tên trong tên hàm khởi tạo còn viết thường (waffle) trong tên hàm bình thường.

Hoặc trong hàm hãy luôn trả về đối tượng mà bạn mong muốn thay vì để mặc định là this

var Waffle = function() {
  var type = {};
  type.tastes = "yummy";
  return type;
}
hoặc 
var Waffle = function() {
  return {
    tastes = "yummy"
  };
}
kết quả: 
var first = new Waffle();
var second = Waffle();
console.log(first.tastes) // "yummy"
console.log(second.tastes) // "yummy"

Vấn đề với pattern này là liên kết đến prototype của các đối tượng bị mất. Bất kỳ phương thức nào bạn thêm vào Waffle() prototype đều không khả dụng cho các đối tượng được tạo ra.

Trên đây là một chút kiến thức mà mình khám phá được và cảm thấy thú vị trong cuốn sách JavaScript Pattern muốn chia sẻ đến với mọi người. Đây là một cuốn sách rất hay và còn rất nhiều thứ bổ ích trong cuốn sách, mình sẽ chia sẻ thêm những điều này trong quá trình mình châm cứu về cuốn sách này. Hãy đợi mình !!

Tài liệu tham khảo:

http://sd.blackball.lv/library/JavaScript_Patterns_%282010%29.pdf

Add a Comment

Scroll Up