+ All Categories
Home > Documents > 324-JavaScript Nang Cao - Design Patterns

324-JavaScript Nang Cao - Design Patterns

Date post: 22-Oct-2014
Category:
Upload: huynh-thi-minh-thu
View: 121 times
Download: 1 times
Share this document with a friend
15
Áp d ng Design Patterns cho JavaScript Áp dụng Design Patterns cho JavaScript Vũ Mạnh Cường Email: [email protected] Nội dung Áp dụng tư tưởng của Design Patterns vào JavaScript. Tìm hiểu cách thực thi của các thư viện, framework cho JavaScript. Chú ý Khi copy các mã JavaScript để chạy phải thay thế các dấu nháy kép “” và nháy đơn '' cho đúng nếu không sẽ không chạy được. 1
Transcript
Page 1: 324-JavaScript Nang Cao - Design Patterns

Áp d ng Design Patterns cho JavaScriptụ

Áp dụng Design Patterns cho JavaScriptVũ Mạnh CườngEmail: [email protected]

Nội dungÁp dụng tư tưởng của Design Patterns vào JavaScript.Tìm hiểu cách thực thi của các thư viện, framework cho JavaScript.

Chú ýKhi copy các mã JavaScript để chạy phải thay thế các dấu nháy kép “” và nháy đơn '' cho đúng nếu không sẽ không chạy được.

1

Page 2: 324-JavaScript Nang Cao - Design Patterns

Áp d ng Design Patterns cho JavaScriptụ

Mục lục1.Các tiêu chí khi sử dụng Design Patterns............................................................................................32.Một số kỹ thuật....................................................................................................................................3

Mở rộng mảng...................................................................................................................................3Tạo namespace..................................................................................................................................4

Cách 1: Sử dụng lớp Object..........................................................................................................4Cách 2: Sử dụng mảng bằng cặp ngoặc bao '{}'...........................................................................6Gợi ý.............................................................................................................................................7

Kiểm tra xem thuộc tính/ phương thức có thuộc object hay không..................................................7Kiểm tra sự tồn tại của thuộc tính.................................................................................................7Kiểm tra sự tồn tại của phương thức.............................................................................................8

3.Áp dụng Design Patterns.....................................................................................................................8Lập trình cho interface hơn là implementation..................................................................................8

Observer Pattern...........................................................................................................................8Timer...........................................................................................................................................11

Nên dùng composition hơn là inheritance.......................................................................................14

2

Page 3: 324-JavaScript Nang Cao - Design Patterns

Áp d ng Design Patterns cho JavaScriptụ

1. Các tiêu chí khi sử dụng Design PatternsCó 4 tiêu chí:

✗ Refactor✗ Lập trình cho interface hơn là implementation✗ Nên dùng composition hơn là inheritance✗ Delegate

Bài viết này đề cập đến interface và composition. Với 'Refactor' thì ít dùng, 'Delegate' có thể áp dụng khi bạn muốn tạo ra các GUI component bắt mắt.

2. Một số kỹ thuậtĐể áp dụng interface vào JavaScript cần một số kỹ thuật.

➢ Mở rộng mảngVí dụ/* Có mảng hash A như sau: */var A = {

id: 1,name: 'A',show: function() {

alert('id = '+this.id+'\nname = '+this.name);}

}/* Sử dụng mảng hash A */A.show();/*---------------Kết quả:

id = 1name = A

*/

Có thể mở rộng mảng A như sau:/* Mở rộng mảng A */A['nextId'] = function() {

this.id++; // tăng id lên 1this.name = 'B';// đổi tên

}A['hello'] = function() {

alert('Xin chào, tôi là '+this.name+'!');}/* Sử dụng mảng A mở rộng */A.show();A.nextId();A.show();A.hello();/*---------------

3

Page 4: 324-JavaScript Nang Cao - Design Patterns

Áp d ng Design Patterns cho JavaScriptụ

Kết quả:id = 1name = Aid = 2name = BXin chào, tôi là B!

*/

Có thể sáp nhập 2 object vào bằng hàm sau:/* Sáp nhập 2 object */// obj1: object 1// obj2: object 2// Sau khi sáp nhập thì obj1 = obj1+obj2// Phần trùng nhau giữa obj1 và obj2 thì sẽ lấy của obj2function extend(obj1, obj2) {

for (var x in obj2) obj1[x] = obj2[x];}

➢ Tạo namespaceKhi viết thư viện hoặc framework thì thường hay dùng namespace. Có nhiều cách tạo namespace nhưng ở đây chỉ nói đến 2 cách như sau:

Cách 1: Sử dụng lớp ObjectVì Object là 1 lớp của JavaScript nên nhớ là chữ 'O' phải viết hoa. Khai báo namespace là một biến global và tạo mới đối tượng Object. Khi truy cập các thuộc tính hoặc gọi các phương thức thì sử dụng từ khóa 'this'.Ví dụ/* Tên namespace là 'my_namespace' */var my_namespace = new Object();

/* Khi đưa thêm các thuộc tính hoặc object khác vào 'my_namespace' thì sử dụng dấu chấm '.' */

// Khai báo biếnmy_namespace.CONST = 113;my_namespace.name = 'Tôi là một namespace';

// Khai báo hàmmy_namespace.show = function() {

alert(this.name);}

// Khai báo mảngmy_namespace.event = {

alert: function() {alert('Nếu có vấn đề gì hãy gọi số '

+my_namespace.CONST+' :D');

4

Page 5: 324-JavaScript Nang Cao - Design Patterns

Áp d ng Design Patterns cho JavaScriptụ

/* Không thể gọi: 'this.CONST' vì'this' ở đây là 'my_namespace.event' */

},byebye: function(name) {

alert('Tạm biệt '+name+'!');}

}

// Khai báo lớpmy_namespace.Class = function() {

this.id = 'Class';this.say = function() {

alert('Tôi là: '+this.id);}

}my_namespace.Class.prototype = {

hello: function() {alert('Xin chào!');this.say();

}}my_namespace.Class.prototype.ask = function() {

this.hello();alert('Bạn là ai thế?');

}

/* Sử dụng namespace */

// Lấy giá trị của biến trong namespacealert(my_namespace.CONST);

// Gọi hàm trong namespacemy_namespace.show();my_namespace.event.alert();my_namespace.event.byebye('Tom');

// Sử dụng lớp trong namespacevar c = new my_namespace.Class();c.ask();

/*---------------Kết quả:

113Tôi là một namespaceNếu có vấn đề gì hãy gọi số 113 :DTạm biệt Tom!Xin chào!Tôi là: ClassBạn là ai thế?

*/

5

Page 6: 324-JavaScript Nang Cao - Design Patterns

Áp d ng Design Patterns cho JavaScriptụ

Cách 2: Sử dụng mảng bằng cặp ngoặc bao '{}'Sử dụng mảng để tạo namespace.Ví dụ trên sẽ được viết lại như sau:/* Tên namespace là 'my_namespace' */var my_namespace = {

// Khai báo biếnCONST: 113,name: 'Tôi là một namespace',

// Khai báo hàmshow: function() {

alert(this.name);},

// Khai báo mảngevent: {

alert: function() {alert('Nếu có vấn đề gì hãy gọi số '

+my_namespace.CONST+' :D');/* Không thể gọi: 'this.CONST' vì'this' ở đây là 'my_namespace.event' */

},byebye: function(name) {

alert('Tạm biệt '+name+'!');}

}, // Kết thúc mảng event

// Khai báo lớpClass: function() {

this.id = 'Class';this.say = function() {

alert('Tôi là: '+this.id);}

}}

/*Bổ sung thêm phương thức cho lớp 'Class' trong 'my_namespace'*/my_namespace.Class.prototype = {

hello: function() {alert('Xin chào!');this.say();

}}my_namespace.Class.prototype.ask = function() {

this.hello();alert('Bạn là ai thế?');

}

6

Page 7: 324-JavaScript Nang Cao - Design Patterns

Áp d ng Design Patterns cho JavaScriptụ

/* Sử dụng namespace */

// Lấy giá trị của biến trong namespacealert(my_namespace.CONST);

// Gọi hàm trong namespacemy_namespace.show();my_namespace.event.alert();my_namespace.event.byebye('Tom');

// Sử dụng lớp trong namespacevar c = new my_namespace.Class();c.ask();

/*---------------Kết quả:

113Tôi là một namespaceNếu có vấn đề gì hãy gọi số 113 :DTạm biệt Tom!Xin chào!Tôi là: ClassBạn là ai thế?

*/

Gợi ýNên kết hợp 2 cách tạo namespace ở trên. Với namespace có nhiều biến, hàm, lớp bên trong thì sử dụng cả Object để giảm số lượng ngoặc bao '{}' và tránh viết mảng quá dài (khó đọc và khó debug).

➢ Kiểm tra xem thuộc tính/ phương thức có thuộc object hay khôngSử dụng từ khóa typeof để kiểm tra xem thuộc tính/ phương thức có tồn tại hay không.

Kiểm tra sự tồn tại của thuộc tínhif (typeof object['tên_thuộc_tính'] == 'undefined') {

// object không có thuộc tính này} else { // object có thuộc tính này

// Lấy giá trị thuộc tính bằng lệnh sau:object['tên_thuộc_tính']

}

Hàm kiểm tra thuộc tính như sau:// obj: object// name: tên thuộc tínhfunction contain(obj, name) {

return (!obj || (typeof obj[name] == 'undefined')) ? false :

7

Page 8: 324-JavaScript Nang Cao - Design Patterns

Áp d ng Design Patterns cho JavaScriptụ

true;}

Hàm lấy giá trị của thuộc tính:/* Dùng hàm này khi muốn đặt giá trị mặc định trong trường hợp thuộc tính không tồn tại */// obj: object// name: tên thuộc tínhfunction getAttribute(obj, name) {

var att = obj[name];// 0 trong dòng lệnh tiếp theo là giá trị mặc địnhreturn (!obj || (typeof att == 'undefined')) ? 0 : att;

}

Kiểm tra sự tồn tại của phương thức// Nếu object có phương thứcif (typeof object['tên_phương_thức'] == 'function') {

// Gọi phương thức bằng lệnh sau:// value là các tham sốobject['tên_phương_thức'](value1, value2...);// Hoặc lệnh này:// value là các tham sốobject.tên_phương_thức(value1, value2...);

} else {// object ko có phương thức này

}

Hàm thực thi phương thức của object bất kỳ như sau:// obj: object// fname: tên phương thức// opt: các tham số của phương thứcfunction exec(obj, fname, opt) {

if (obj && (typeof obj[fname] == 'function')) obj[fname](opt);}

Với việc gọi phương thức bằng hàm exec như trên sẽ giúp giả lập interface trong JavaScript.

3. Áp dụng Design Patterns

➢ Lập trình cho interface hơn là implementationTôi sẽ viết 2 ví dụ về sử dụng interface. Đọc xong 2 ví dụ này thì bạn sẽ biết giả lập interface như thế nào trong JavaScript.

Observer PatternNếu bạn đã biết Design Patterns thì chắc là cũng biết Observer Pattern. Dưới đây tôi sẽ thực hiện

8

Page 9: 324-JavaScript Nang Cao - Design Patterns

Áp d ng Design Patterns cho JavaScriptụ

pattern này bằng JavaScript. Trong pattern này có sử dụng interface, và để dùng interface trong JavaScript thì cần dùng hàm 'exec' trong phần '2. Một số kỹ thuật'.Ví dụ/*----- Các hàm hỗ trợ cho Observer Pattern -----*/

/* Thực thi phương thức của object */// obj: object// fname: tên phương thức// opt: các tham số của phương thứcfunction exec(obj, fname, opt) {

if (obj && (typeof obj[fname] == 'function')) obj[fname](opt);}

/* Sử dụng hằng số này để lưu thuộc tính nhãn cho object */SEAL_ATTR = '__seal';SEAL_INDEX = 0;

/* Dán nhãn cho object */function seal(obj){

obj[SEAL_ATTR] = 'ix'+(SEAL_INDEX++);}

/* Trả về nhãn của một object */function getSeal(obj){

return (obj) ? obj[SEAL_ATTR] : undefined;}

/* Kiểm tra xem obj có thuộc tính att không */function contain(obj, att){

return (obj && (typeof obj[att] != 'undefined')) ? true : false;}

/* Kiểm tra xem obj có đc dán nhãn không */function containSeal(obj){

return this.contain(obj, SEAL_ATTR);}

/*----- Khai báo Observer Pattern -----*/Observable = function(){

this.observers ={};}Observable.prototype ={

add: function(ob){if (ob){

if (!containSeal(ob)) seal(ob);this.observers[getSeal(ob)] = ob;return true;

}return false;

9

Page 10: 324-JavaScript Nang Cao - Design Patterns

Áp d ng Design Patterns cho JavaScriptụ

},remove: function(ob){

if (containSeal(ob)){var id = getSeal(ob);if (contain(this.observers, id)) {

this.observers[id] = null;}return true;

}return false;

},removeAll: function(){

for (var x in this.observers) {this.observers[x] = null;

}},notify: function(){

for (var x in this.observers) {exec(this.observers[x], 'update', this);

}}

}

/* Sử dụng Observer Pattern */// Khai báo các observerfunction Observer() {

this.update = function() {alert('Observer.update');

}}var ob1 = new Observer();var ob2 = {

update: function() {alert('ob2.update');

}}

// Tạo đối tượng Subjectvar subject = new Observable();subject.add(ob1);subject.add(ob2);subject.notify();subject.remove(ob1);subject.notify();/*---------------Kết quả:

Observer.updateob2.updateob2.update

*/

10

Page 11: 324-JavaScript Nang Cao - Design Patterns

Áp d ng Design Patterns cho JavaScriptụ

Vì trong lớp Observable cho phép remove 1 observer ra khỏi danh sách các observer đã đăng ký nên phải đưa thêm vào các observer thuộc tính '__seal' (dán nhãn). Việc này giúp cho có thể phân biệt được các observer khác nhau trong danh sách observer. Bạn nhìn lại ví dụ và thấy rất nhiều hàm trợ giúp liên quan đến 'seal', thực ra đây là 1 thủ thuật để phân biệt các object.

Trong ví dụ trên thì lớp Observable (còn gọi là Subject) có interface như thế này:Observable {

add: function(observer)remove: function(observer)removeAll: function()notify: function()

}

Và các lớp Observer sẽ có interface như thế này:Observer {

// Có thể ko có tham số subjectupdate: function(subject: Observable)

}

Khi sử dụng Observer Pattern bạn sử dụng pattern trên sẽ thấy nó rất thú vị và tiện dụng. Nếu dùng các open source framework như Prototype chẳng hạn thì Observer Pattern là 1 trong những lớp core. Về cơ bản thì họ cũng thực hiện Observer Pattern như ví dụ nêu trên.

TimerKhi muốn chạy một nội dung gì đó theo thời gian hoặc theo 1 số lần nào đó và có trễ (delay time) giữa 2 lần chạy liên tiếp thì hãy sử dụng Timer. Lớp Timer dưới đây chạy được trên IE, FireFox, Netscape, chưa thử với Safari, Opera.Ví dụ<html><head><script language=JavaScript>/* Trả về thẻ html */function $(id) {

return document.getElementById(id);}

/* Các hàm hỗ trợ cho Timer */

/* Sử dụng hàm bind để truyền tham chiếu của object khi kết hợp với các hàm về thời gian hay sự kiện bàn phím/chuột (nếu ko sẽ ko dùng được các phương thức của object). Hàm này được mượn từ Prototype framework. */Function.prototype.bind = function(obj){

var __method = this;return function(){

__method.apply(obj, arguments);

11

Page 12: 324-JavaScript Nang Cao - Design Patterns

Áp d ng Design Patterns cho JavaScriptụ

}}

/* Sáp nhập 2 object */// Sau khi sáp nhập thì obj1 = obj1+obj2// Phần trùng nhau giữa obj1 và obj2 thì sẽ lấy của obj2function extend(obj1, obj2) {

for (var x in obj2) obj1[x] = obj2[x];}

/* Thực thi phương thức của object */// obj: object// fname: tên phương thức// opt: các tham số của phương thứcfunction exec(obj, fname, opt) {

if (obj && (typeof obj[fname] == 'function')) obj[fname](opt);}

/* Khai báo lớp Timer */Timer = function(options){

// Khai báo các tham số mặc địnhthis.opt ={

speed: 0,times: 0

}// Sử dụng hàm 'extend' để// lấy giá trị các tham số đưa vào lớp Timerextend(this.opt, options);this._timer = null;this._cnt = 0;this._itv = true;this._exec = null;

}// Khai báo các phương thức của lớp TimerTimer.prototype ={

// Bắt đầu thực thi Timertrigger: function(){

if (!this.opt.times) return;this._cnt = 0;this._exec = (this.opt.times > 0) ?

this._finiteLoop.bind(this) : this._loop.bind(this);if (typeof window.setInterval == "undefined"){

this._itv = false;this._timer = window.setTimeout(

this._exec, this.opt.speed );} else {

this._itv = true;this._timer = window.setInterval(

this._exec, this.opt.speed );}

12

Page 13: 324-JavaScript Nang Cao - Design Patterns

Áp d ng Design Patterns cho JavaScriptụ

},// Kết thúc Timer// Nếu enable==true thì thực hiện hàm kết thúc 'actionFinish'// Nếu enable==false thì ko làm gì cảkill: function(enable){

if (this._itv) window.clearInterval(this._timer);else window.clearTimeout(this._timer);this._exec = null;if (enable) exec(this.opt, "actionFinish");

},// Khi lặp có giới hạn thì dùng hàm này// để tính số lần lặp_finiteLoop: function(){

if (++this._cnt > this.opt.times) this.kill(true);else this._loop();

},// Khi lặp ko giới hạn (mãi mãi) thì dùng hàm này_loop: function(){

exec(this.opt, "action");if (!this._itv) {

this._timer = window.setTimeout(this._exec, this.opt.speed );

}}

}

/* Sử dụng lớp Timer */window.onload = function(){

// Dùng biến counter để kiểm tra hoạt động của Timervar counter = 0;

// Tham số đầu vào của Timer mà một mảng// Thứ tự các phần tử mảng ko quan trọng// Mảng tham số ko cần đầy đủ (thiếu thì cũng ko bị error)var t = new Timer({

speed: 10, // delay time in msaction: function() { // được gọi qua mỗi lần lặp

$('log').value = counter++;},actionFinish: function() { // được gọi khi kết thúc Timer

$('log').value = counter;},times: 100 // Số lần lặp

});

// Bắt đầu thực thi Timert.trigger();// Nếu muốn kết thúc bất thường thì sử dụng:// t.kill(true);// hoặc

13

Page 14: 324-JavaScript Nang Cao - Design Patterns

Áp d ng Design Patterns cho JavaScriptụ

// t.kill();}</script></head><body><input id='log' type=text size=30></body></html>

Trong ví dụ trên lớp Timer có interface như thế này:Timer = {

Timer: function(action: Action); // constructortrigger: function()kill: function(enable)

}

Và các lớp thực hiện hành động (Action) sẽ có interface như thế này:Action = {

speed: n // Độ trễtimes: n // Số lần lặp. Nếu < 0 thì sẽ lặp vô hạnaction: function() // Xử lý từng lần lặpactionFinish: function() // Xử lý kết thúc Timer

}

Lớp Timer rất linh hoạt và tiện dụng trong việc tạo hiệu ứng, làm giao diện đẹp, bắt mắt. Trong các thư viện, open source framework của JavaScript cũng rất hay có lớp Timer nhưng tất nhiên các lớp đó có thể được viết khác với ví dụ nêu trên.

➢ Nên dùng composition hơn là inheritanceTrong JavaScript có thể thừa kế lớp (tham khảo bài viết rất hay này: Classical Inheritance in JavaScript). Tuy nhiên nếu có thể thì nên sử dụng composition, vừa nhẹ nhàng vừa đỡ phải viết mã phức tạp.Ví dụ JavaScript có lớp Date xử lý ngày tháng nhưng lớp này không hỗ trợ kiểm tra năm nhuận. Thêm nữa tháng trả về của Date bắt đầu từ 0, ngày bắt đầu từ 1. Có thể mở rộng lớp Date ra để sử dụng mà không cần thừa kế như sau:/* Mở rộng lớp Date */// Hàm tạo DateExt có thể được gán tham số có kiểu DateDateExt = function(d){

var _dt = (d instanceof Date)? d : new Date();this.Date = _dt;/* Kiểm tra xem có phải năm nhuận không */this.isLeapYear = function(){

var y = _dt.getFullYear();return ( (y%4==0) && ((y%100!=0)||(y%400==0)) )

? false : true;}this.setFullYear = function(y, m, d){

14

Page 15: 324-JavaScript Nang Cao - Design Patterns

Áp d ng Design Patterns cho JavaScriptụ

_dt.setFullYear(y, m-1, d);}this.getDate = function(){

return _dt.getDate();}this.getMonth = function(){

return _dt.getMonth()+1;}this.getFullYear = function(){

return _dt.getFullYear();}this.increase = function(n){

_dt.setDate(_dt.getDate()+n);}

}

/* Sử dụng lớp DateExt */var dx = new DateExt();// Lấy Date objectvar d = dx.Date;alert(d);// Kiểm tra xem năm hiện tại có phải năm nhuận hay không?alert(dx.isLeapYear()? 'năm nhuận' : 'năm thường');// Tăng lên 30 ngày xem saodx.increase(30);var s = dx.getFullYear()+'-'+dx.getMonth()+'-'+dx.getDate();alert(s);

OK, không nhiều nhưng hy vọng bài viết này giúp ích cho bạn. Qua các ví dụ ở trên bạn sẽ hiểu hơn về cách thực hiện các thư viện và open source framework và áp dụng chúng tốt hơn. Nếu tự viết mã JavaScript thì giờ hẳn là bạn đã có thêm những lựa chọn mới cho thực đơn của mình.Chúc ngon miệng!

15


Recommended