What have you found for these years?

2007-11-13

ActionScript 3.0 closure and scope

本來以為 action script 3.0 已經改掉很多問題了,
無奈其實問題還是很多。之前最容易碰到的問題,就是 dynamic scoping 的
問題。在 closure (lambda function) 時的 scope 不是 lexical scope,
而是 caller 端的 scope. 這導致:

button.on_click = function(){ this.start(); }

會動不了,因為 this 會 refer button 而不是寫這段程式時的 scope.
而 name check 卻是 lexical scope.....可怕吧,compile time 與
runtime scope 上的差異,這可是會嚇死人的!

因此,個人經常會寫這種奇怪的作法:

button.self = this;
button.on_click = function(){ self.start(); }

edited:
或是
var self = this;
button.on_click = function(){ self.start(); }

這樣就可以 work 了 :)
另外值得注意的是,在 button 中呼叫的 self.start 不受 access control,
這邊 start 就算是 private 同樣可以呼叫。這個問題...比起 scope 大概算小。

後來才知道,macromedia 是有出 lib 去對付這個問題。

button.on_click = Delegate.create(this, function(){ this.start(); })

這樣就不會有 dynamic scope 的問題了。不過,很煩啊,而且這根本不直覺。

action script 3.0 中修掉這個問題了,現在預設使用 lexical scope,
總算 runtime 跟 compile time 有一致的 name check 了。
無奈在 closure 中的 variable 並無法真的達到 closure, 請見下範例:

var test = []
for(var i=0; i<10; ++i)
  test.push(function(){trace(i)})

for each(var ii in test)
  ii.call()

現在我們產生 10 個 closure, 存到 test array 中,
接著依次呼叫他們。希望可以得到的結果是:

0
1
2
3
...
8
9

但是答案會是:

10
10
10
10
...
10
10

因為 closure 並沒有發生作用,function 中仍然指回 i,
而 i 的最終結果將會是 10, 因為 i 到達 10 時跳出第一個迴圈。
這件事讓我 debug 蠻久才發現的.....
也就是說,這邊要非常小心 side effect !!
所有 function 將會互相影響!

我想這可能也是為什麼在第二個迴圈中 ii 無法使用 i 這個名字的原因,
因為 i 這個名字如果不保留的話,closure 將無法找回原本的 variable !
好像不應該用 closure 稱呼他了,是吧?

p.s. for each 是 as3 新加入的東西,原本的 for in loop 跟
javascript 一樣,ii 會是 index, 這樣實在有點麻煩,也大出我的意料。
幸好 as3 有 for each 這種東西了。還是 ruby 好,有 each,
each_with_index, 還有 each_index 可用。

0 retries:

Post a Comment

All texts are licensed under CC Attribution 3.0