Function declaration style and variable scope problem

เป็นปัญหาที่ยังไม่สามารถทำให้เกิดขึ้นอีกในโค้ดแบบอื่นได้ แต่เอามาเขียนไว้ก่อนเพราะมันสร้างความปวดหัวให้กับคนเขียน 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
      }
    }

ปัญหาทุกอย่างก็หายไปอย่างปริศนา ทิ้งไว้แต่เครื่องหมายคำถามว่าทำไมขอบเขตของฟังก์ชั่นถึงต่างกันเพียงแค่เปลี่ยนรูปแบบการประกาศฟังก์ชั่นเท่านั้น และไม่สามารถทดลองซ้ำกับการเขียนโค้ดแบบง่าย ๆ ได้ แต่หลังจากนี้รู้แล้วว่า จะไม่ประกาศฟังก์ชั่นในรูปแบบตัวแปรอีก เพราะไม่อยากเจอปัญหาแปลกๆ แบบนี้ให้นั่งปวดหัวหาวิธีแก้อีกแล้ว

About llun

Just a programmer

, ,

  • http://www.teerapap.net teerapap.c

    ผมสงสัยว่า แล้ว checkboxChange แบบที่บอกใช้ไม่ได้อะครับ ถ้าเรียกตรงๆ ไม่ได้เรียกผ่าน eventListener เรียกทำงานได้รึเปล่าครับ?

    • http://llun.info llun

      ได้เพราะ scope ตอนเรียกอยู่ในคลาสแล้ว

  • http://lluv.info IIUV

    เอ๊ะ หรือมันจะเป็น bug ของ Flash builder beta 4???