はてブのようにTweetする機能作ったよ!
はてブでブックマークするときに押すボタンってかなり便利だよね。
これがなかったら、わざわざURLをコピーして、はてブのサイトに行って、書き込みっていう作業をしないといけない。
Twitterでもそれは同じで、あるサイトに対してつぶやきたい場合は、URLをコピー、Twitterのサイトに行く、書き込みっていう作業が必要だよね。
じゃあはてブのボタンの用にTweetするボタン作ればよくね?
作った
10分とかからなかった。
Google Chorome、Firefox、Operaで動作確認済み。
javascript:(function(){window.open("http://twitter.com/?status="+location.href);})();
上のJavaScriptをブラウザのブックマークに追加すれば完成!
あとは、サイトに対して突っ込みたいときにTweet!を押せば、Twitterのページに飛べる。
もとのサイトのURLがすでに入っているので、それに突っ込みを追加して投稿!
これでより、tweetしやすくなったね。
より幸せなTwitterライフを送ってください。では!
追記(タイトル有バージョン)
"twitter"にてタイトルもあった方がさらにいいのでは?とのコメントをいただいたので、タイトル有バージョンも作った。
javascript:(function(){window.open("http://twitter.com/?status=+"+encodeURI(document.title)+location.href);})();
場合によって、好きな方を使えばいいと思うよ。
さらに追記(タイトル判別バージョン)
もっと便利なの作ってもらった。やったー。
http://skdontl.blogspot.com/2009/09/tweetlet.html
タイトルが有るか無いかを自動的に判別してくれる。みんな、これ使えばいいよ!
try catchでスコープが可能!?
AS3ではブロック単位のスコープは無くて、スコープが欲しい場合は、関数クロージャでくくったりする。でもそれ以外にも方法があったらしいね。
偶然にもtry catchでもスコープがあることを発見した。
なんだこれ?
package { import flash.display.Sprite; import flash.text.TextField; public class Main extends Sprite { public function Main():void { var xs:Array = []; try { throw 0; } catch (i:int) { for (; i < 5; i++ ) { xs.push(i); } } var sum:int = 0; try { throw 0; } catch (i:int) { for (; i < xs.length; i++ ) { sum += xs[i]; } } var tf:TextField = new TextField(); addChild(tf); tf.text = "sum" + sum; //trace(i);//コンパイルエラー } } }
http://wonderfl.net/code/47bb13e129e7eee92a0a556bbe430b78bb8cb29d#251374
iに注目。
catch(i:int)で宣言しているiのスコープはcatchの中になっているっぽい。
つまりcatchの外からはiは見えない。
何でこうなるかはわかんない。仕様なのか?バグなのか?
ひとまずこれで、これで関数内でスコープを自由自在に使いこなすことが出来るね!
...ってことにはならない。
そもそも例外を例外以外で使おうとするなんて、問題外。
可読性めっちゃ悪いし。
例えば、スコープの付いた変数を2個定義しただけでも、
try { throw 0; } catch (i:int) { try { throw 0; } catch (j:int) { for (i=0; i < 5; i++ ) { for (j=0; j < 5; j++ ) { trace(i, j); } } } }
と、ネストがひどい。
面白い機能だけど、くれぐれも遊び以外では使わないように!
追記
どうやらこれはECMAScripの仕様らしいです。
ECMA262規格で決められた仕様です。AS3はECMA262の拡張言語なのでECMAの仕様は踏襲していると思います。JavaScriptも同様だけど、これが正しく実装されていない処理系もあったりします(特にJScript)。
関数クロージャ使えばprivateなメンバが消えてなくなるよ!
クロージャネタでいつまで引っ張る気だよ!って声がそろそろ聞こえてきそう・・・。
俺のクロージャネタは108式まであるぞ!
ActionScript書いてて余計なメンバが増えることって無い?
数個なら気にならないけど、それが増えてきたら気持ち悪いよね。
関数クロージャを使いこなせば余計なメンバも定義する必要もなくなるぜ!
関数クロージャを使わなかった場合
例えば60〜0までのカウントをTextFieldで表示するものを作ろうとした場合。
package { import flash.display.Sprite; import flash.events.Event; import flash.text.TextField; public class Main extends Sprite { private var _tf:TextField; private var _count:int; public function Main():void { _count = 60; _tf = new TextField(); addChild(_tf); addEventListener(Event.ENTER_FRAME, onEnterFrame); } private function onEnterFrame(e:Event):void { _tf.text = "count" + _count--; if (_count < 0) { removeEventListener(Event.ENTER_FRAME, onEnterFrame); } } } }
wonderfl build flash online | 面白法人カヤック
このように書くのがふつー。
ただ、_tfと_countはonEnterFrame()の中でしか使ってないのに、
メンバとして宣言しないといけないのはちょっと微妙。
関数クロージャを使った場合
package { import flash.display.Sprite; import flash.events.Event; import flash.text.TextField; public class Main extends Sprite { public function Main() { var tf:TextField = new TextField(); addChild(tf); var count:int = 60; addEventListener(Event.ENTER_FRAME, function(e:Event):void { tf.text = "count" + count--; if (count < 0) { removeEventListener(Event.ENTER_FRAME, arguments.callee); } }); } } }
関数クロージャを使った場合 | wonderfl build flash online
あれ?tfとcountがメンバじゃなくなったぞ!
ついでにonEnterFrame()も消えちゃった!
コードを見ると、関数クロージャの中からtfやcountにアクセスしている。
これは、レキシカルスコープという関数クロージャ特有のスコープのおかげです。
関数クロージャの中から外の変数が見える。
このレキシカルスコープと関数クロージャを使えば、コンストラクタ以外のすべてのメンバがprivateならば、
メンバをすべて無くすことも可能です。
関数クロージャすっげ!
関数クロージャの中でthisを使ってはならない
そーいえば、前twitter上でFlasherたちが、メンバを呼ぶときにthis付ける派か付けない派で議論してたね。
私の場合、thisを付けいないとわかりにくくなる場合は付ける。
じゃあ、関数クロージャの中では、this付けてメンバ呼び出す?
いやいや、関数クロージャの中では、thisがどれを指すかは実行時に決まるので基本的にthisは使わないよ。
関数クロージャの中ではthisが変わってしまう
メソッドの内部ではthisを付けようが付けまいが、変数名が被ってない限り、動作は変わらない。
なので、thisは読みやすさを重視するために付けるようなもんだ。
しかし、これが関数クロージャとなると話は別だ。
下の例で何が起こるのかわかるだろうか?
package { import flash.display.Sprite; public class Main extends Sprite { public function Main():void { (function():void { this.f(); })(); } private function f():void { trace("f() called!"); } } }
きっと、「f() called!」って出力が出るんだよね!
残念ハズレ。
「thisにfっていう関数はねーよ!」というランタイムエラーが出る。
これはthisが指している対象が違うため。
じゃあthisって何を指しているの?
次の例で、traceで何が出力されるのか予想してみよう。
package { import flash.display.Sprite; public class Main extends Sprite { public function Main():void { trace("1 " + this); (function():void { trace("2 " + this); })(); var o:Object = new Object(); o.f = function():void { trace("3 " + this); } o.f(); } } }
答え
1 [object Main]
2 [object global]
3 [object Object]
1はメソッド内に書かれているので、ふつーにMainのオブジェクトを指している。
2,3が関数クロージャ内でthisが使われているので、挙動が違う。
2がglobalというよくわからない場所を指していて、
3が、oを指している。
thisが変わる理由
thisが指している対象が変わってしまうのかは、一応理由がある。
ASも昔はJavaScriptのようなプロトタイプベースのオブジェクト指向言語だった。
それが、AS2.0、AS3.0になり、クラスベースのオブジェクト指向プログラミング(OOP)もサポートするようになった。
メソッドクロージャの内部でthisが変わるのは、そのプロトタイプベースのOOPの名残とも言える機能だ。
なので、AS3でもプロトタイプベースのOOPをやろうと思ったら出来なくはないが、
クラスベースのOOPの方が優れているので誰もやらない。
なので、この関数クロージャの内部でthisが変わるというのは、クラスベースのOOPをしている限り、
うっとうしいだけであり、邪魔で、意味のない機能だ。
AS3で誰もプロトタイプベースのOOPをしてないんだから、この機能は消えるべきだと思う。
であるからして、AS3は基本的にメソッドクロージャ内部ではthisを付けないこと推奨する。
ちなみにAS2までは、メソッドもthisの対象が変わってしまうというやっかいな問題があった。
Delegate.create()という関数を使ってその問題に対処していた。
それでもthisを使いたいです!
それでも、ローカル変数とメンバ名が被った場合など、thisを使いたい場合があるだろう。
何通りか対処の方法はある。
一番簡単で一般的な方法が、thisを関数クロージャの外で変数に入れておく方法。
package { import flash.display.Sprite; public class Main extends Sprite { public function Main():void { var f:Function = function():void { trace("local f() called!"); } var self:Main = this; (function():void { f(); self.f(); })(); } private function f():void { trace("member f() called!"); } } }
上の例ではselfという変数を使って代わりに使用している。
これなら、指している対象が変わってしまうということにはならない。
一安心だ。
他にもFunction#call()とかを使えば出来るけど、あんまり使わないので説明は省略。
クロージャをイベントに登録したら、どうやってイベントから削除するの?
無名関数使うと、removeEventListener()出来ないんじゃないの?
っていうコメントがあったので。
それぐらい出来るよ!
その1 一時変数に入れておく
誰でも思いつく方法。
変数に入れて変数名でアクセスすれば解決。
public function Main():void { var f:Function = function(e:Event):void { trace("hello"); removeEventListener(Event.ENTER_FRAME, f); } addEventListener(Event.ENTER_FRAME, f); }
実行してみると、"hello"、と一回出るだけ。
え?いちいち変数作るのめんどくさいって?
その2 arguments.calleを使う
無名関数自身にアクセスする方法はないだろうか?
もちろんあるよ!arguments.calleつかえばいいよ!
public function Main():void { addEventListener(Event.ENTER_FRAME, function(e:Event):void { trace("hello"); removeEventListener(Event.ENTER_FRAME, arguments.callee); }); }
arguments.calleというのは、現在実行している関数への参照。
上の例では、arguments.calleの中に無名関数が入っている。
ちなみに、メソッドでもクロージャでもどちらの場合でも使える。
arguments.calle使えば無名関数で再起も出来るよ!
(function(i:int):void { trace(i); if (i > 0) { arguments.callee(i - 1); } })(5); // => 5 4 3 2 1 0
メソッド、メソッドクロージャ、関数クロージャ
前の記事、ミスったああああ。
メソッドクロージャじゃなくて、関数クロージャだった!
修正しといた。
メソッドとメソッドクロージャと関数クロージャって紛らわしいよね!
ということでまとめ。
関数クロージャとは?
クラスに属していない関数のこと。
無名関数で作られたり、関数の中で宣言したやつがこれにあたる。
様は動的に作られる関数。
public function foo():void { var f:Function = function():void { trace("f()");} function g():void { trace("g()"); } f(); g(); }
上の例では、fに束縛(代入)された関数や、gが関数クロージャにあたる。
当然、fとかgとかはfoo()の外からはアクセスできない。
ちなみにtrace()や、getTimer()も、クラスに属していないので、これも関数クロージャである。
(TODO:あとで補足書く)
メソッドクロージャとは?
メソッドをFunctionオブジェクトとして扱う場合、それをメソッドクロージャっていう。
public function Main():void{ addEventListener(Event.ENTER_FRAME,onEnterFrame); } private function onEnterFrame(e:Event):void { trace("enter framce"); }
addEventListener()の引数に入れているonEnterFrameがそれにあたる。
というのが
AS3のクロージャ | www.imajuk.swf
に書いてあったよ!
ActionScript3.0で関数クロージャ使えると便利だよ!
ActionScriptでは、関数クロージャ使う場合と、使わない場合では、だいぶ書き方に差が出る。
関数クロージャ使った方が、綺麗に、短く書けるようになる。
ということで、メソッドクロージャ使ったパターンを紹介するよ!
関数クロージャってなに?っていう人は以下のサイトを読めばわかるかも。
Adobe ActionScript 3.0 * 関数のスコープ
メソッドクロージャとバインドメソッド at AS3S.ORG
その1 イベントのリスナーとして使う
イベントリスナーを作るとき、わざわざメンバとして作ってやるのはめんどくさい。
そんなときは関数クロージャ使えばいいよ!
普通に書いた場合
package { import flash.display.Sprite; import flash.events.Event; public class Main extends Sprite { public function Main():void { addEventListener(Event.ENTER_FRAME, onEnterFrame); } private function onEnterFrame(e:Event):void { trace("ENTER FRAME !"); } } }
関数クロージャを使用した場合
package { import flash.display.Sprite; import flash.events.Event; public class Main extends Sprite { public function Main():void { addEventListener(Event.ENTER_FRAME, function(e:Event):void { trace("ENTER FRAME !"); }); } } }
関数クロージャを使えば結構コードがすっきりして、可読性が上がる。
その2 スコープとして使う
ASのスコープは関数単位。
Javaとは違ってブロック単位でのスコープはないよ。
つまり関数内では同じ変数を二個作ることは出来ないわけだ。
例えば関数内でfor文を二回以上使いたい場合、はループのための変数名を二個作るか、
変数を使いまわさないといけない。
public function Main():void { var xs:/*int*/Array = []; for (var i:int = 0; i < 5; i++ ) { xs.push(i); } var sum:int = 0; for (var j:int = 0; j < xs.length; j++ ) { sum += xs[j]; } trace(sum); }
この例だとiとjという2つの変数を使っている。
ただ、この書き方は、下のfor文で間違ってiを使ったりして、間違えやすい。
これを関数クロージャでくくってやると、スコープが働くので、
別々の関数クロージャなら同じ変数を宣言しても問題なくなる。
public function Main():void { var xs:/*int*/Array = []; (function():void{ for (var i:int = 0; i < 5; i++ ) { xs.push(i); } })(); var sum:int = 0; (function():void{ for (var i:int = 0; i < xs.length; i++ ) { sum += xs[i]; } })(); trace(sum); }
iを別々の場所で二回宣言しても、コンパイラに怒られることはない。
その3 配列の高階関数の引数として使う
例えば、配列の要素すべてに5を足した配列を作りたいとしよう。
普通にやるなら、for文で回すのが一般解だろう。
var xs:/*int*/Array = [0, 1, 2, 3, 4, 5]; var outs:/*int*/Array = []; for (var i:int = 0; i < xs.length; i++) { outs.push(xs[i] +5); } trace(outs);
しかし、配列にはそれにぴったりな関数が用意されてる。map()だ。
var xs:/*int*/Array = [0,1,2,3,4,5]; var outs:/*int*/Array = xs.map( function(x:int, index:int, xs:/*int*/Array):int { return x + 5; } ); trace(outs);
ちなみにこれはもっと短く書ける。
trace([0, 1, 2, 3, 4, 5].map(function(x:int, ...rest):int { return x + 5; } ));
すっごい短く書けるよ!
配列には他にも、便利な関数が何個もある。
filter()とか、every()とか、some()とか。
ちなみに、map()とか、filter()とか、引数に関数をとる関数を高階関数っていうらしいよ。
関数クロージャ便利だからどんどん使っていこうぜ!