เป็นปัญหาที่ยังไม่สามารถทำให้เกิดขึ้นอีกในโค้ดแบบอื่นได้ แต่เอามาเขียนไว้ก่อนเพราะมันสร้างความปวดหัวให้กับคนเขียน Javascript มาก่อนมากมาย และไม่รู้จะเจอมันอีกทีเหมื่อไหร่ แต่ไม่สามารถเอาโค้ดต้นฉบับทั้งก้อนมาใส่ในนี้ได้ เลยขอแค่เล่าแล้วเอา stacktrace มาแปะใส่ไว้ละกัน
เหตุการณ์เริ่มต้นเกิดจาก @pitiphong_p เจอบั๊กหนึ่งที่ไม่รู็จะแก้อย่างไรเข้าเพราะใน Flash Builder Debugger บอกว่าตัวแปรต่างๆ มีค่าครบสมบุรณ์หมด แต่ error บอกไม่สามารถหาตัวแปรนั้นได้
[Fault] exception, information=TypeError: Error #1010: A term is undefined and has no properties.
Fault, CheckboxListRenderer.as:21
21 data.selected = checkbox.selected
(fdb) bt
#0 this = [Object 32588233, class='global'].(event=[Object 659079665, class='flash.events::Event']) at CheckboxListRenderer.as:21
#1 EventDispatcher/dispatchEventFunction() at :0
#2 this = [Object 655130785, class='mx.controls::CheckBox'].EventDispatcher/dispatchEvent(_arg1=[Object 659079665, class='flash.events::Event']) at :0
#3 this = [Object 655130785, class='mx.controls::CheckBox'].UIComponent/dispatchEvent(event=[Object 659079665, class='flash.events::Event']) at UIComponent.as:9440
#4 this = [Object 655130785, class='mx.controls::CheckBox'].Button/http://www.adobe.com/2006/flex/mx/internal::setSelected(value=true, isProgrammatic=false) at Button.as:1204
#5 this = [Object 655130785, class='mx.controls::CheckBox'].Button/clickHandler(event=[Object 591642521, class='flash.events::MouseEvent']) at Button.as:2798
ตอนแรกก็พยายามคาดเดาปัญหาไปต่าง ๆ อาจเกิดจากการ bind ตัวแปรผิดที่ หรือตอนกำหนดค่าผิด แต่ถ้าอย่างนั้น ทำไมตอน debug ถึงสามารถเอาค่าออกมาดูได้หละ ลองดูโค้ดเจ้าปัญหาซักนิดก่อนละกัน
package sample
{
import mx.binding.utils.BindingUtils
import mx.containers.HBox
import mx.controls.Alert
import mx.controls.CheckBox
import mx.core.IFactory
import flash.events.Event
public class CheckboxListRenderer extends HBox {
[Bindable]
public var listRenderer:IFactory
public var checkbox:CheckBox = new CheckBox()
private var instance:*
private var checkboxChange = function(event:Event):void {
if (data.hasOwnProperty('selected')) {
data.selected = checkbox.selected
}
}
public override function set data(item:Object):void {
super.data = item
if (item && item.hasOwnProperty('selected')) {
checkbox.selected = item.selected
}
}
protected override function createChildren():void {
super.createChildren()
addChild(checkbox)
if (listRenderer != null) {
instance = listRenderer.newInstance()
addChild(instance)
}
trace("Checkbox list renderer created children")
}
protected override function commitProperties():void {
super.commitProperties()
horizontalScrollPolicy = "off"
verticalScrollPolicy = "off"
// Handle change in targeting phase for changing data selected flag.
checkbox.addEventListener(Event.CHANGE, checkboxChange)
BindingUtils.bindProperty(instance, "data", this, "data")
}
}
}
รับรองได้เลยว่าถ้าใครเอาโค้ดด้านบนไปลองเล่นดูเองจะไม่เจอปัญหาแน่นอน ซึ่งถึงวันนี้ยังไม่เข้าใจเหมือนกันว่าทำยังไงถึงจะเกิดปัญหาได้ แต่วิธีแก้นั้นง่ายมากคือ เปลี่ยนวิธีประกาศฟังก์ชั่นจาก
private var checkboxChange = function(event:Event):void {
if (data.hasOwnProperty('selected')) {
data.selected = checkbox.selected
}
}
เป็น
private function checkboxChange(event:Event):void {
if (data.hasOwnProperty('selected')) {
data.selected = checkbox.selected
}
}
ปัญหาทุกอย่างก็หายไปอย่างปริศนา ทิ้งไว้แต่เครื่องหมายคำถามว่าทำไมขอบเขตของฟังก์ชั่นถึงต่างกันเพียงแค่เปลี่ยนรูปแบบการประกาศฟังก์ชั่นเท่านั้น และไม่สามารถทดลองซ้ำกับการเขียนโค้ดแบบง่าย ๆ ได้ แต่หลังจากนี้รู้แล้วว่า จะไม่ประกาศฟังก์ชั่นในรูปแบบตัวแปรอีก เพราะไม่อยากเจอปัญหาแปลกๆ แบบนี้ให้นั่งปวดหัวหาวิธีแก้อีกแล้ว