ก็เลยไปค้นหาดูบทความดีๆ ที่พูดเรื่อง closure พบบทความที่น่าสนใจก็คือ
Javascript Closures
หลังจากนั่นอ่่าน(ด้วยความทรมาน) อยู่พักใหญ่ ก็เลยเกิดแรงบันดาลใจว่า
เห็นควรจะได้ฤกษ์ทำความเข้าใจในเรื่อง Javascript ของตัวเองเสียที (หลีกเลี่ยงมานานแล้ว)
Note: ข้อควรระวัง. Javascript นี้อ้างอิง ECMAScript เป็นหลัก
ไม่ได้ดูเรื่อง browser compatiblilty เลย
Execution Context
Note: คำอธิบายนี้เป็น abstract
การ implement ของ Javascript Engine จริงๆ อาจจะมี object model
ซับซ้อนกว่านี้
Execution Context ก็คือ Object ธรรมดานี่แหล่ะ
เมื่อเราเริ่มต้น run javascript
เจ้า javascript engine ก็จะทำการ initialize "Global Execution Context"
ให้เรา โดยมี method (หรือ function) จำนวนหนึ่งมาให้ เช่น Date, encodeURI, isNAN, ...
(พวกนี้ก็คือ build in function ของ javascript นั่นเอง)
เมื่อไรก็ตามที่เรามีการ call function
เจ้า Javascript engine ก็จะทำการสร้าง Execution Context Object
ตัวใหม่ให้เรา และทำการ push current Execution Context ลงใน
Stack ก่อน เพื่อที่ว่าเมื่อมีการ return ออกจาก function แล้ว
ก็จะ pop เอา Execution Context ที่อยู่ใน Stack ขึ้นมาเป็น current Execution Context ต่อไป
ลองดูตัวอย่าง
1: function callMe() {
2: var x = 10;
3: }
4:
5: a = 'hi';
6: callMe();
เมื่อเริ่มต้น run Javascript นี้.
เจ้า Javascript Engine ก็จะทำการ
Initialize new Global Execution Context ขึ้นมา.
เมื่อ Engine อ่านเจอบรรทัดที่ 1 ที่เป็นการประกาศ function
ก็จะทำการ add function นี้เข้าเป็นสมาชิก (method)
ของ Global Execution Context
ส่วนที่บรรทัดที่ 5 ที่มีการ initialize variable.
variable
a
ตัวนี้จะถูกสร้างเป็นสมาชิก (property) ของcurrent scope ซึ่งก็คือ Global Execution Context นั่นเอง
ที่บรรทัดที่ 6 มีการ call ไปที่ function
callMe
JavaScript Engine ก็จะสร้าง Execution Context ใหม่ขึ้นมา,
จากนั้นก็ push Global Execution Context ลง stack ก่อน
สุดท้ายก็ set Execution Context ตัวใหม่นี้ เป็น current Execution Context
ที่บรรทัดที่ 2 มีการ initialize variable
x
variable
x
จะถูกสร้างให้เป็นสมาชิกของ currentExecution Context และเมื่อจบจาก function
callMe
current Execution Context ก็จะถูก Garbage ทิ้งไป (ตัวแปร x
ก็จะหายไป) และทำการ pop Execution Context จาก stack
ขึ้นมา set เป็น current Execution Context แทน
__proto__
ตัวนี้เป็น internal Object Reference ซึ่งชี้ไปยัง Object ที่เราต้องการให้เป็นต้นแบบ
(ทุก Object ภายใน javascript จะมี property __proto__ เสมอ)
ดูตัวอย่างดีกว่า เข้าใจง่ายกว่า
1 : function Fruit() {
2 : this.eatable = true;
3 : }
4 : function Apple() {
5 : this.color = "red";
6 : }
7 : Apple.prototype = new Fruit();
8 : var apple1 = new Apple();
9 : print (apple1.color); // ==> red
10: print (apple1.eatable); // ==> true
11: print (apple1.x);
ที่บรรทัดที่ 7 เรากำหนดให้
prototype
propertyของ Apple Function ไห้ชี้ไปยัง Object ที่สร้างขึ้นมาจาก Object Fruit
เมื่อไรก็ตามที่เราเรียกมีการ
new Apple()
Instance ที่สร้างขึ้นใหม่นี้จะถูก set ค่า __proto__ ให้ชี้ไปยัง
ค่าที่กำหนดไว้ใน
prototype
propertyที่นี้คำถาม ก็คือ ทำไมต้องมี __proto__
__proto__ จะถูกใช้เมื่อเรามีการ access properties ของ Object นั้น
แล้ว JavaScript Engine ไม่สามารถหา property ชื่อที่เราต้องการใน Object นั้นได้
เจ้า Javascript engine ก็จะขยับออกไปหา property นั้นจาก
Object ที่ __proto__ ชี้อยู่แทน ถ้าหาไม่เจออีก ก็ขยับขึ้นไปอีก
จนกว่า จะเจอค่า __proto__ ที่เป็น null
จากข้างบนตัวอย่างข้างบน เราสามารถเขียนแบบกำหนด
__proto__
ได้ตรงๆดังนี้
function Fruit() {
this.eatable = true;
}
function Apple() {
this.color = 'red';
}
y = new Apple();
z = new Fruit();
y.__proto__ = z;
print (y.color);
print (y.eatable);
ข้อควรระวังในการใช้ Inner Function
จากตัวอย่างนี้
function Position(x, y) {
this.dump = function() {
println ("x = " + x + ", y = " + y);
}
}
p = new Position(10, 20);
p.dump();
การที่เราประกาศแบบนี้ ทุกครั้งที่เรา new Position ขึ้นมา
จะเกิด Object dump ขึ้นมาด้วย ถ้ามี Position 100 instance
ก็จะมี Object dump 100 instance ด้วยเช่นกัน
ดังนั้นเป็นการดีกว่าที่จะกำหนดเป็น prototype แทนเพื่อให้ลดการใช้ memory ลง
function Position(x, y) {
this.x = x;
this.y = y;
}
Position.prototype.dump = function () {
println ("x = " + this.x + ", y = " + this.y);
}
p = new Position(10, 20);
p.dump();
ปัญหาอีกอย่างของ inner function ก็คือ
memory leak
function add(x) {
return function innerAdd(y) {
return x + y;
}
}
a = add(10);
println (a(2)); // ==> 12
การที่เรากำหนด function add ไว้อย่างนี้
ทำให้เกิดปัญหา memory leak ได้
เนื่องจากในขณะที่เรา new function innerAdd
ขึ้นมานั้น innerAdd จะมี reference ไปยัง
กลุ่มของ object ที่อยู่ใน execution context จำนวนหนึ่ง
และเมื่อมีการ return ค่ากลับออกมา
Garbage ก็จะพยายาม release resources
ที่ถูกจองใน Execution Context ของ funcion add
แต่ก็ไม่สามารถ release ได้ (เพราะยังมี pointer จาก a อ้างถึงอยู่)
วิธีที่ดีกว่า ก็คือ
function add(x) {
this.x = x;
return addAgain;
}
function addAgain(y) {
return this.x + y;
}
a = add(10);
println (a(2)); // ==> 12
3 comments:
อ่านดูแล้วเหมือนเป็น 301 มากกว่า 101 นะ (แบบว่าไม่ค่อยเข้าใจ)
ให้ดีต้องอ่านไป debug ไป
(ใช้ http://edenti.deis.unibo.it/Ling/2004-2005/rhino/online/JavaScriptIDE.html ช่วย debug)
เห็นด้วยว่า อ่านยาก (link เรื่อง closure)
คนเขียนเค้าเขียนประโยคยาว ๆ โดยไม่มีเครื่องหมายวรรคตอนซักกะติ๊ด อ่านแล้วปวดหัว T-T
Post a Comment