Sunday, August 21, 2005

Javascript 101

อ่านเจอ Idea ของ 'bact ในเรื่องการใช้ closure ในการ modify tree structure แล้ว
ก็เลยไปค้นหาดูบทความดีๆ ที่พูดเรื่อง 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 จะถูกสร้างให้เป็นสมาชิกของ current
Execution 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

Related link from Roti

3 comments:

Unknown said...

อ่านดูแล้วเหมือนเป็น 301 มากกว่า 101 นะ (แบบว่าไม่ค่อยเข้าใจ)

PPhetra said...

ให้ดีต้องอ่านไป debug ไป
(ใช้ http://edenti.deis.unibo.it/Ling/2004-2005/rhino/online/JavaScriptIDE.html ช่วย debug)

bact' said...

เห็นด้วยว่า อ่านยาก (link เรื่อง closure)

คนเขียนเค้าเขียนประโยคยาว ๆ โดยไม่มีเครื่องหมายวรรคตอนซักกะติ๊ด อ่านแล้วปวดหัว T-T