โพสนี้เป็นโพสที่ผมดองมายาวเหยียดดด (เพิ่งสังเกตว่าตั้งกะกันยาปีที่แล้ว) เนื่องจากควรจะต้องเข้าใจเรื่อง Call/Apply, Binding และ Closure ก่อน ถึงจะมาโพสนี้ได้ครับ
เรื่องมันมีอยุว่า ใน JavaScript ถ้าเราจะสร้าง Object ที่เก็บ State ได้ เรามักจะใช้อยู่ 2 เทคนิคครับ นั่นคือใช้ Object เลยไม่งั้นก็ใช้ Closure ซึ่งแต่ละอันก็มีข้อดีข้อเสียแตกต่างกันไป ออริจินัลโพสจาก Skilldrick ครับ
อ้อ ตัวอย่างทั้งหมดนี้ ดูแบบ Live Example ได้ที่นี่นะครับ
สร้าง Object Builder / Constructor
ลองมาดูแบบใช้ Object กันก่อน ซึ่งมันก็คือการสร้าง Constructor นั่นเองครับ
|
1 2 3 4 5 6 7 8 |
// Object
var Person1 = function(name) {
this.name = name;
};
Person1.prototype.sayHi = function() {
console.log('Hi, my name is ' + this.name);
}; |
มาดูแบบใช้ Closure กันบ้าง
|
1 2 3 4 5 6 7 8 |
// Closure
var Person2 = function(name) {
return {
sayHi: function() {
console.log('Hi, my name is ' + name);
}
};
}; |
คราวนี้ก็สร้าง Instance จาก Constructor (หรือ Object Builder) สองตัวนี้
|
1 2 3 4 5 6 7 8 9 10 11 |
var a1 = new Person1('a1');
a1.sayHi(); // a1
var a2 = new Person1('a2');
a2.sayHi(); // a2
var b1 = new Person2('b1');
b1.sayHi(); // b1
var b2 = new Person2('b2');
b2.sayHi(); // b2 |
แล้วมันแตกต่างกันยังไงละหว่า
เรื่องของเรื่องคือ ถ้าใช้ Object แล้ว State จะถูกใช้ผ่าน this เวลาที่เราเรียก Method เช่น a1.sayHi() this ก็จะเป็นตัว Object นั้น
แต่ถ้าเป็น Closure State จะถูกเก็บไว้ใน Lexical Scope ซึ่งมันต่างกันของ Object กันอีตอนที่เราจะไปจัดการกับไอ State เนี่ยแหละครับ
This highlights the first key difference between objects and closures: access to the internal state.
เรามีวิธีการเปลี่ยนค่าใน State ของ Object ได้ 3 วิธี เช่นต่อไปนี้
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
// 1. Obtain a reference to the object and assign new properties
var changeName1 = function(obj, newName) {
obj.name = newName;
};
changeName1(a1, 'a11');
a1.sayHi(); // a11
// 2. Attach a function to the object and call it as a method on the object
var changeName2 = function(newName) {
this.name = newName;
};
a1.changeName = changeName2;
a1.changeName('a111');
a1.sayHi(); // a111
// 3. call/apply a function with the object as the context
changeName2.call(a1, 'a1111');
a1.sayHi(); // a1111 |
แต่เราจะใช้ทั้งสามวิธีนี้กับ Closure ไม่ได้ครับ
|
1 2 3 4 5 6 7 8 9 |
changeName1(b1, 'b11');
b1.sayHi(); // b1
b1.changeName = changeName2;
b1.changeName('b111');
b1.sayHi(); // b1
changeName2.call(b2, 'b22');
b2.sayHi(); // b2 |
การที่เราจะทำได้ ต้องสร้าง Method นั้นไว้ใน Closure นั้นด้วย แบบนี้
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
// Closure
var Person3 = function(iname) {
var name = iname;
return {
changeName: function(newName) {
name = newName;
},
sayHi: function() {
console.log('Hi, my name is ' + name);
}
};
};
var c = new Person3('c');
c.sayHi(); // c
c.changeName('c1');
c.sayHi(); // c1 |
แล้วข้อดีข้อเสียของแต่ละแบบล่ะ ?
ข้อดีของแบบ Object
จากที่เห็นว่าเราสามารถเปลี่ยนแปลงค่าใน State ของ Object ได้ 3 วิธีข้างบน จะสังเกตว่า เราสามารถสร้าง Method เหล่านี้จากที่ไหนก็ได้ แล้วค่อยมายัดใส่เอา แตกต่างจาก Closure ที่ต้องสร้าง Method เหล่านี้จากใน Object Builder ที่เอาไว้สร้าง Closure ของมันเท่านั้น
ข้อดีอีกข้อคือในแง่ของ Memory ถ้าเป็น Closure การที่เราเอา Method ไปไว้ใน Object Builder ทำให้เวลาเราสร้าง Instance ใหม่ แต่ละตัวก็จะมี Method ของใครของมัน ไม่เหมือนกับของ Object ที่เราสร้าง Method เหล่านี้ครั้งเดียว ทำงานผ่าน this แล้วก็ใส่ให้กับ Instance ไหนก็ได้ ไม่งั้นก็แชร์ผ่าน prototype เลยก็ยังได้อีก
ข้อดีของแบบ Closure
การที่บอกว่าแบบ Object สามารถใส่ Method จากที่ไหนก็ได้ ถ้ามองอีกมุมนึงก็คือเราเสียความเป็นส่วนตัว (Privacy) ของเราไป ใครก็สามารถสร้าง Method แล้วก็มายัดใส่เมื่อไหร่ก็ได้ ซึ่งในขณะที่ Closure ถ้าจะแก้ก็ต้องมาแก้ใน Object Builder ของ Closure เท่านั้น
อีกอย่างคือเราไม่ต้องกังวลกับ this ซึ่งต้องห่วงเรื่อง Binding ด้วย
งี้ควรจะเลือกแบบไหนดี ?
So, when should you use objects and when closures? If you’re making hundreds of object-type things then they should probably be objects. If there are only a few and you have security concerns then closures are a better bet.
ตามนั้นแหละครับ ผมว่ามันขึ้นอยุกับสถานการณ์ ถ้าสมมติผมจะเขียนอะไรให้คนอื่นใช้ก็คงต้องเป็น Closure แต่ถ้าเป็นทั่วไปก็ใช้ Object ได้ไม่มีปัญหาครับ
I prefer closure wa. But prototype sharing is near-impossible using closures so if you need some kind of inheritance, I guess the only (or easier) way is to use prototypes.
btw. just changed versez model.js to use prototype just because of that. huhu.