Ctrl + Click で検索
テキストボックスを Ctrl + Click すると, 親 form を submit.
// ==UserScript== // @name post ctrl click // @namespace d.hatena.ne.jp/arikui // @include * // @require http://gist.github.com/29681.txt // ==/UserScript== $X("//input[@type='text' or not(@type)]").forEach(function(element){ if(element.form) element.addEventListener("click", function(e){ if(e.ctrlKey) element.form.submit(); }, false); });
ALC で使おうと思っていたのに上の方のテキストボックスが form の中にないというクソ HTML でダメでした. → input.form ならいけるそうです.
追記
os0xさんがずっとシンプルに書き直したやつ。
http://gist.github.com/219324
クロージャ, call, apply
http://d.hatena.ne.jp/arikui/20090928/1254140257
var o = {r: 1};
1. クロージャ
o.f = function(){ var self = this; (function(n){ self.r += n; })(2); };
2. call
o.f = function(){ (function(n){ this.r += n; }).call(this, 2); };
3. apply (callのやつの単なる置き換え)
o.f = function(){ (function(n){ this.r += n; }).apply(this, [2]); };
4. apply (argumentsを使う)
o.f = function(){ (function(n){ this.r += n; }).apply(this, arguments); }; // o.f(2);
結果
firefox | Chrome | IE | Opera | |
1 | 0.0002546 | 0.0006892 | 0.006588 | 0.002508 |
2 | 0.0002748 | 0.0004598 | 0.007134 | 0.00312 |
3 | 0.000806 | 0.000684 | 0.009875 | 0.004784 |
4 | 0.0056621 | 0.000458 | 0.012456 | 0.004944 |
Firefox の 4, 5 については結果が安定しなかったので, 〜10%減ぐらいに落ち着くかもしれない. それでも結構差は出るだろう.
全体的な傾向としてはスコープ辿るより call, apply で this を置き換えた方が遅いということになる. ただ, Google Chrome の結果はそれとは逆になっていて面白い.
次のような, 引数の配列を予め用意しておくコード.
o.a = [2]; o.f = function(){ (function(n){ this.r += n; }).apply(this, this.a); };
Firefox | Chrome |
0.0003414 | 0.000592 |
あれ, Firefox も速くなってね?みたいなことになる. よく分からないので自分でご確認ください.
ifとスコープの速さ
ifとor
書き方として気になったんですが、
if(!b) b = 2;
a = b + c;
と
a = (b || (b = 2)) + c;
と、if文を書くかどうかで速度に違いが出るのかなー、と思って計ってみました。
// case 1 var a, b, c = 1; while(i--){ if(!b) b = 2; a = b + c; b = null; } // case 2 var a, b, c = 1; while(i--){ a = (b || (b = 2)) + c; b = null; }
fx3.5 | ie8 | opera10 | chrome3.0 | |
case 1 | 0.00001313 | 0.0002628 | 0.0001864 | 0.00002484 |
case 2 | 0.00001188 | 0.0002962 | 0.0002122 | 0.00003132 |
(単位はms)
どれも体感で差が出るような違いはありませんが、Firefoxだけifの方が遅いみたいですね。
スコープ
Firefoxで計ってて思ったんですが、変数のスコープも速度に影響あるんだなあ、と思ってこっちも計ってみました。
// case 3 var a, b, c = 1; while(n--) (function(){ a = (b || (b = 2)) + c; b = null; })(); // case 4 while(n--) (function(){ var a, b, c = 1; a = (b || (b = 2)) + c; b = null; })();
fx3.5 | ie8 | opera10 | chrome |
0.003435 | 0.003586 | 0.001582 | 0.0004042 |
0.0001866 | 0.002802 | 0.001396 | 0.0003785 |
(単位はms)
どのブラウザでも変数定義よりスコープ辿る方がコスト高いようです。特にFirefoxだと大きな差が。俺のFirefoxどっか悪いんだろうか。
まとめ
- ifでもorでも速度的に大差なさそうだし好きに書くのがいいです
- 変数定義のコストを考えるよりスコープチェーンを気にした方がいいです
ツリーマップ型の音楽再生機
最近は Ruby-Processing を使って「ビジュアライジングデータ」をやってます。
GitHub - jashkenas/ruby-processing: Code as Art, Art as Code. Processing and Ruby are meant for each other.
Ruby-Processing のいいところ
音楽再生機
スケッチから iTunes をコントロールして音楽を再生するやつ。
スケッチから iTunes をコントロールするインタフェースは COM なんですが、JRuby では Win32OLE が使えないみたいなので、コントロールするだけのスクリプトを書いてスケッチから実行させてます。
`cscript "#{exec_file}" "#{artist}" "#{album}" "#{song}"`
他にも、細かい曲のデータなんかも、拡大時にスクリプトを実行して取得しています。(一時的に動作が止まって変な感じになる)
全体的に動作が変な感じですけど、「ビジュアライジングデータ」のコードが訳分からんので仕方ないです。ソースコードを GitHub に置いておきましたが、ベースは「ビジュアライジングデータ」の7章と一緒です。
http://github.com/arikui/treemap_player/tree/master
ツリーマップのアルゴリズム自体は JavaScript でも実装できそうなので、Processing.js を使って色々なデータをツリーマップで表現して遊べそうですね。
Rubyで書いてJavaScriptで使う 2(managed codeを使いやすくする)
前回の続き。
http://d.hatena.ne.jp/arikui/20090428/1240866008
前は、
(なんちゃら).Invoke("呼ぶメソッドの名前", [引数])
という風でしたがやっぱり使いづらい。Ruby のオブジェクトをそのまま JavaScript にしたような感じで使いたい。ということで、多少の変更を加えます。
変更点
メソッドをLambdaで登録
http://d.hatena.ne.jp/arikui/20090305/1236259323
Lambda で登録すれば柔軟にオブジェクトが作れるしブラウザ側からメンバが見えます。
引数の渡し方を改善
ブラウザ - Silverlight 間の可変長引数の渡しが狂ってるなら、その辺は JavaScript に任せます。
new Function("return arguments.callee.Invoke(arguments)");
みたいなのを介して呼び出せば、Silverlight 側には常に ScriptObject が引数として渡されるようになります。実際に Managed Code を呼び出してるのは、この Function オブジェクトに登録されてる Invoke メソッドです。
Public Delegate Function F(Of T, TResult)(ByVal args As T) As TResult Public Function ToScriptObject() As Browser.ScriptObject Dim obj As Browser.ScriptObject = window.CreateInstance("Object") Dim names As Collections.Generic.IList(Of String) = operations.GetMemberNames(result) For i = 0 To names.Count - 1 Dim name As String = names(i) Dim fun As F(Of Browser.ScriptObject, Object) = Function(args As Browser.ScriptObject) Invoke(name, args) Dim method As Browser.ScriptObject = window.CreateInstance("Function", "return arguments.callee.Invoke(arguments)") method.SetProperty("Invoke", fun) obj.SetProperty(name, method) Next Return obj End Function
デモ
http://arikui.github.com/run_ruby/TestPage.html
見えるぞ、私にもメソッドが見える。
コード
Public Class ScriptableRuby Private Shared window As Browser.HtmlWindow = Browser.HtmlPage.Window Private Shared runtime As Microsoft.Scripting.Hosting.ScriptRuntime = IronRuby.Ruby.CreateRuntime Private Shared engine As Microsoft.Scripting.Hosting.ScriptEngine = runtime.GetEngine("IronRuby") Private Shared scope As Microsoft.Scripting.Hosting.ScriptScope = engine.CreateScope Private Shared operations As Microsoft.Scripting.Hosting.ObjectOperations = engine.CreateOperations(scope) Public Shared Function Create(ByVal source As String) As RubyObject Dim result = engine.Execute(source, scope) Return New RubyObject(ScriptableRuby.operations, result) End Function Public Shared Function Arrayify(ByVal args As Browser.ScriptObject, Optional ByVal arity As Integer = -1) As Object() Dim length As Integer = args.GetProperty("length") arity = IIf(arity < 0, length, arity) Dim _args(arity - 1) As Object For i As Integer = 0 To arity - 1 _args(i) = args.GetProperty(i) Next Return _args End Function <Browser.ScriptableType()> _ Public Class RubyObject Private result As Object Public Delegate Function F(Of T, TResult)(ByVal args As T) As TResult Public Delegate Function F(Of TResult)() As TResult Public Sub New(ByVal operations As Microsoft.Scripting.Hosting.ObjectOperations, ByVal result As Object) Me.result = result End Sub Public Function ToScriptObject() As Browser.ScriptObject Dim obj As Browser.ScriptObject = window.CreateInstance("Object") Dim names As Collections.Generic.IList(Of String) = operations.GetMemberNames(result) For i = 0 To names.Count - 1 Dim name As String = names(i) Dim fun As F(Of Browser.ScriptObject, Object) = Function(args As Browser.ScriptObject) Invoke(name, args) Dim method As Browser.ScriptObject = window.CreateInstance("Function", "return arguments.callee.Invoke(arguments)") method.SetProperty("Invoke", fun) obj.SetProperty(name, method) Next Return obj End Function <Browser.ScriptableMember()> _ Public Function Invoke(ByVal name As Object, Optional ByVal args As Browser.ScriptObject = Nothing) As Object Dim member As IronRuby.Builtins.RubyMethod = operations.GetMember(result, name) If args Is Nothing Then Return engine.Operations.Invoke(member) Else Return engine.Operations.Invoke(member, ScriptableRuby.Arrayify(args, member.Info.GetArity())) End If End Function <Browser.ScriptableMember()> _ Public Function Invoke(ByVal name As Object, ByVal ParamArray args As Object()) As Object Return engine.Operations.Invoke(operations.GetMember(result, name), args) End Function End Class End Class
Rubyで書いてJavaScriptで使う
AgDLRのRepl面白いですね。
CodePlex Archive
ブラウザ上で Ruby を試せる「IRBWEB」を作ってみた - てっく煮ブログ 跡地
Replは名前のとおりブラウザ上でレプるためのパーツって感じでしょうか。Replの実装は大体こんな感じでやってるんだと思います。
http://blog.tomasm.net/2009/02/20/multilingual-repl/
それを参考にして、Rubyで作ったオブジェクトをJavaScriptで利用するサンプル。
http://arikui.github.com/run_ruby/TestPage.html
オブジェクト登録後に
(なんちゃら).Invoke("呼ぶメソッドの名前", [引数])
みたいな感じでFirebugなどから呼び出してみてください。
ソース
Public Class ScriptableRuby Private Shared window As Browser.HtmlWindow = Browser.HtmlPage.Window Private Shared runtime As Microsoft.Scripting.Hosting.ScriptRuntime = IronRuby.Ruby.CreateRuntime Private Shared engine As Microsoft.Scripting.Hosting.ScriptEngine = runtime.GetEngine("IronRuby") Private Shared scope As Microsoft.Scripting.Hosting.ScriptScope = engine.CreateScope Private Shared operations As Microsoft.Scripting.Hosting.ObjectOperations = engine.CreateOperations(scope) Public Shared Function Create(ByVal source As String) Dim result = engine.Execute(source, scope) Return New RubyObject(ScriptableRuby.operations, result) End Function <Browser.ScriptableType()> _ Public Class RubyObject Private result As Object Public Sub New(ByVal operations As Microsoft.Scripting.Hosting.ObjectOperations, ByVal result As Object) Me.result = result End Sub <Browser.ScriptableMember()> _ Public Function Invoke(ByVal name As Object) As Object Return engine.Operations.Invoke(operations.GetMember(result, name)) End Function <Browser.ScriptableMember()> _ Public Function Invoke(ByVal name As Object, ByVal args As Browser.ScriptObject) As Object Dim length As Integer = args.GetProperty("length") Dim _args(length - 1) As Object For i As Integer = 0 To length - 1 _args(i) = args.GetProperty(i) Next Return engine.Operations.Invoke(operations.GetMember(result, name), _args) End Function <Browser.ScriptableMember()> _ Public Function Invoke(ByVal name As Object, ByVal ParamArray args As Object()) As Object Return engine.Operations.Invoke(operations.GetMember(result, name), args) End Function End Class End Class
ScriptableRuby.Create()は、Rubyのコードを実行して返ってきた値をJavaScriptで利用可能なScriptableRuby.RubyObjectとして返します。返ってくる値はコードの最後の行のものなので、「A.new」とか最後に書いとかないと利用できません。(そうしなくてもいい方法はあるんでしょうかね)
ScriptableRuby.RubyObjectはInvoke()でRubyのオブジェクトのメソッドを呼び出します。(サンプルではメソッドのみしか利用できません。多分)
ここで問題なのは、引数にParamArrayを指定してもJavaScript → Silverlightでちゃんと渡してくれません。なのでJavaScript側で配列として渡すことにしました。実質3つめのInvoke()は使われません。
Text to Canvas
この前のネタ、テキスト手書きとかすると酷いことになるなー、と思ったのでFx3独自仕様を使って文字を描画します。Canvas上をダブルクリック。日本語も可能でした(Windows)。
文字を描く | MDN
座標は適当にやってるので、ちゃんと調整しないとマズイ。
// ==UserScript== // @name Text to Canvas // @namespace http://d.hatena.ne.jp/arikui/ // @include * // ==/UserScript== var context = null; var input = document.body.appendChild(document.createElement("input")); var offset = Math.round(input.getBoundingClientRect().height / 2); input.style.position = "absolute"; input.style.display = "none"; input.addEventListener("keyup", function(e){ if(e.keyCode !== 13) return; var rect = input.getBoundingClientRect(); context.translate(rect.left, rect.top); context.mozDrawText(e.target.value); context.fill(); context.translate(-rect.left, -rect.top); input.value = ""; input.style.display = "none"; }, false); document.body.addEventListener("dblclick", function(e){ if(e.target.tagName.toLowerCase() != "canvas") return; context = e.target.getContext("2d"); input.focus(); (function(style){ style.display = "block"; style.zIndex = getComputedStyle(e.target, "").zIndex * 1 + 1 || "9999"; style.left = e.clientX + "px"; style.top = e.clientY - offset + "px"; })(input.style); }, false);