【JavaScript 】new呼び出し、「this instanceof fNOP」の意味。
■topic summary
study to understand this site "Polyfill" section
Function.prototype.bind() - JavaScript | MDN
事の発端
パーフェクトJavaScript (PERFECT SERIES 4)
- 作者: 井上誠一郎,土江拓郎,浜辺将太
- 出版社/メーカー: 技術評論社
- 発売日: 2011/09/23
- メディア: 大型本
- 購入: 24人 クリック: 588回
- この商品を含むブログ (12件) を見る
bindはapplyもしくはcallを使えば独自に実装可能です
とあった。
ふむふむ。そうなんだ。
Mozillaのリファレンスにも
「bindが定義されていなかったらこのコードを使って代用してね」
とコードが載っている。
The bind function is a recent addition to ECMA-262, 5th edition; as such it may not be present in all browsers. You can partially work around this by inserting the following code at the beginning of your scripts,
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Function/bind
ならやってみよう。
できるかな。
・・・から始まりました。
そして2週間くらい嵌っていました。。。
ここで明らかにすること。
fNOP = function () {}, fBound = function () { return fToBind.apply(this instanceof fNOP && oThis ? this : oThis, aArgs.concat(Array.prototype.slice.call(arguments))); }; fNOP.prototype = this.prototype; fBound.prototype = new fNOP(); return fBound;
このコードが何をやっているのか理解する。
・どういう時に「this instanceof fNOP」がtrue?
・fBoundのプロトタイプにfNOPのインスタンスを代入?
など。
まずは基本。
ケース1:new 呼び出しのthisコンテキスト
生成されるインスタンス自身が「this」にsetされます。
http://qiita.com/Haru39/items/9935ce476a17d6258e27
具体的に書いてみると、
Foo = function(){ console.log(this); } Foo.prototype.arg="ppp"; x = new Foo(); y = Foo();
■実行結果
Foo {arg: "ppp"} Window {top: Window, window: Window, location: Location, Proxy: Object, external: Object…}
(ブラウザで試行しているため、2個目はグローバルオブジェクトとしてwindowオブジェクトが返ってきている)
ケース2:new呼び出し時の返り値
returnで関数を返した場合は、その関数が返り値となる。
関数以外であればインスタンスが返る。
Goo = function(){ return "hoge"; } new Goo()
Foo = function(){ fn = function(){console.log("jjj")}; return fn; }; new Foo() ||< ■実行結果 >|| Goo {} function (){console.log("jjj")}
If the constructor function doesn't explicitly return an object, the object created in step 1 is used instead.
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/new
うーん。Stringもobjectだと思うから、この解説は良く分からないけど。。
本題
ここからが難しくなるところ!
まず、クラス(prototypeプロパティを持つオブジェクト)を返す関数を考える。
この関数をnew呼び出ししたときは、
thisコンテキストが生成されるインスタンスとなるので(∵ケース1)、
protorypeで定義したプロパティを読み取ることができる。
hoge = function () { fBound = function(){ return console.log(this.arg); }; fBound.prototype.arg = "ppp"; return fBound; }; boo = hoge(); x = new boo();
■実行結果
ppp
new呼び出しかどうかを判別する
注)ここからは憶測も入っているので確証ではないです。
探しても同種の内容に言及している資料が見つかりませんでした。
すみません。
自分が分からなかったのは、この
this instanceof fNOP、がtrueになるケース。
だってメソッド呼び出しできないわけだから、
thisは関数呼び出しの時のグローバルオブジェクトになるのでは?、
と思っていたわけです。
コンストラクタ呼び出しのときはthisがその返されるインスタンスになる。
このケースが頭になかったのです。
hoge = function (oThis) { var fNOP = function() {}, fBound = function() { return console.log(this instanceof fNOP); #<= ア }; fBound.prototype = new fNOP(); #<= イ return fBound; }; foo = hoge(); x = new foo(); #<= ウ
■実行結果
true
順に追っていくと、
hoge関数で返却された関数(クラス)をnew呼び出しすると、、
1)ウにてnew呼び出しをするため、
アのthisはfBoundオブジェクトとなる(∵ケース1)。
2)イにて、fBoundはプロトタイプに
fNOP.prototypeを継承したオブジェクトを持つ(※2)
3)(1)(2)より、thisのプロトタイプチェインでfNOP.prototypeを持つため、
アのinstanceofはtrueとなる(※3)
※2
When the code new foo(...) is executed, the following things happen:
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/new
A new object is created, inheriting from foo.prototype.
※3
instanceof 演算子は、object のプロトタイプチェインで constructor.prototype の存在を確認します。
https://developer.mozilla.org/ja/docs/JavaScript/Reference/Operators/instanceof
あらためて、事の発端となったMozillaのコードを見返してみると、、
Function.prototype.bind() - JavaScript | MDN
fBound = function () { return fToBind.apply(this instanceof fNOP && oThis ? this : oThis, #<= (ア) aArgs.concat(Array.prototype.slice.call(arguments))); }; fNOP.prototype = this.prototype; fBound.prototype = new fNOP(); return fBound;
(ア)の三項演算子は、
関数呼び出し(this instanceof fNOPがfalse)⇒第一引数がoThis(bindの第一引数)
new呼び出し(this instanceof fNOPがtrue)⇒第一引数が生成されるインスタンス
という違いを表現している。
new呼び出しをしてもreturnで指定したapply関数の評価結果が返る(∵ケース2)
実際に書いてみると、
hoge = function(){console.log("-----");console.log(this)}; x = {age: 10}; fn = hoge.bind(x); fn(); new fn();
■実行結果
----- Object {age: 10} ----- fBound {}
fn()では thisがx
new fn()では thisがfBoundインスタンス。
何がうれしいの?
fBoundインスタンスだと何がうれしいのか。
fBoundクラスは内部に隠蔽されているわけだし?
答えは
fNOP.prototype = this.prototype; fBound.prototype = new fNOP();
のところ。
fBoundは、thisのプロトタイプを継承している。
つまり、thisのプロトタイプを定義してみると、
hoge = function(){console.log("-----");console.log(this)}; hoge.prototype.arg1 = "hello"; x={age: 10}; fn = hoge.bind(x); fn(); new fn();
■実行結果
----- Object {age: 10} ----- fBound {arg1: "hello"}
new呼び出しのときは、プロトタイプを継承している!
以上を、
・プロトタイプを持っているものをクラスとみなして先頭大文字にする
というルールで見やすく整理すると
Hoge = function(){console.log(this.age)}; Hoge.prototype.age = 999; x={age: 10}; fn = Hoge.bind(x); fn(); new fn();
■実行結果
10 999
どうだ!
まとめ
まとめると、
bindはthisを第一引数に差し替える関数だが、
bindの返り値をnew呼び出しすると、その法則が無視されて、
thisはバインドのレシーバーオブジェクトのインスタンスに差し替えられる。
。。。。つかれたぶひー。
【Ruby】【メタプログラミング】言語内DSL(文法チェックあり)
■topic summary
study about DSL. it has grammer check.
昨日作成した言語内DSLは文法チェックがなかったけど、
今回は文法チェックを追加したパターン。
学んだこと
親クラスで暮らすメソッドを定義することにより、
子クラスで地の文チックに使用できる
⇒elementメソッド
class_eval内でのメソッド定義
class_eval %Q{ def #{tagname}(attributes={} ,&b) tag(:#{tagname}, attributes ,&b) end }
のところ。
なぜtagnameを展開してシンボル化するのか。
第二引数のattributesはなぜ#{}展開がいらないのか
を理解しました。
これは別記事にて詳しく書きました。
裸のハッシュ(※1)の後に
ブロックを続ける場合は{}ではなくdo-endを使う。
※1
6.4.4
http://www.amazon.co.jp/%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0%E8%A8%80%E8%AA%9E-Ruby-%E3%81%BE%E3%81%A4%E3%82%82%E3%81%A8-%E3%82%86%E3%81%8D%E3%81%B2%E3%82%8D/dp/4873113946
ハッシュリテラルがメソッドの最後の引数なら(あるいは、その後ろに続くのがブロック引数だけなら)、ハッシュリテラルを囲む中かっこを省略できるようにしている。中かっこなしのハッシュは、裸のハッシュ(bare hash)と呼ばれることがあり・・・
正式なリファレンスがググりきれなかったので、本からの引用
DSLコード
HTMLForm.generate(STDOUT) do comment "simple form" form name: "registration", action: "http://hoge" do content "Adress:" textarea name: "address", rows:6, cols: 40 do "please enter your address" end end end
■実行結果
<!-- simple form --><form name='registration' action='http://hoge' method='GET' entype='application/www'>Adress:<textarea name='address' rows='6' cols='40' readonly='readonly'>please enter your address</textarea></form>
実装
class XMLGrammer def initialize(out) @out = out end def self.generate(out, &b) self.new(out).instance_eval(&b) end def self.element(tagname, attributes={} ,&b) @allowed_attributes ||= {} @allowed_attributes[tagname] = attributes class_eval %Q{ def #{tagname}(_attributes={} ,&b) tag(:#{tagname}, _attributes ,&b) end } end def self.allowed_attributes @allowed_attributes end def tag(tagname, attributes={}, &b) allowed = self.class.allowed_attributes[tagname] @out << "<#{tagname}" #認められた属性か attributes.each do |key, val| raise "unknown attribute" if !allowed.include?(key) @out << " #{key}='#{val}'" end #デフォルト値を出力 allowed.each do |key, val| next if attributes.include?(key) if val == REQ raise "required attribute missing" elsif val == BOOL @out << " #{key}='#{key}'" elsif val.is_a? String @out << " #{key}='#{val}'" end end @out << ">" if block_given? content = yield @out << content if content end @out << "</#{tagname}>" nil end def comment(text) @out << "<!-- #{text} -->" nil end def content(text) @out << "#{text}" end OPT = :opt REQ = :req BOOL = :bool end class HTMLForm < XMLGrammer element :form, action: REQ, method: "GET", entype: "application/www", name: OPT element :textarea, name: REQ, rows: REQ, cols: REQ, readonly: BOOL end
疲れたぶー。
【Ruby】【メタプログラミング】メソッド名を動的に変更。 class_eval
■topic summary
study about class_eval
変数を展開した値のメソッド名を定義したい
たとえば、こうやって書くと、hogeメソッドが定義される。
kokoメソッドを定義するときはどうすればいいのか?
メソッド内で"koko"を表示するにはどうすればいいのか?
という話。
class Test hoge="koko" def hoge p hoge end end p Test.new.methods
■実行結果
[:hoge, :nil?, :===, :=~, : ・・・
hogeメソッドが定義されてしまっている。
ヒント
こうやって#{}を使うことで文字列内で展開できる。
class Test hoge="koko" p hoge p "#{hoge}" end
■実行結果
"koko" "koko"
でも、このまま使うと構文エラーになる。
class Test hoge="koko" p hoge def "#{hoge}" p hoge end end
■実行結果
syntax error, unexpected tSTRING_BEG
そりゃそうだー。
そこでclass_evalの出番。
class_evalとinstance_evalの違いは前に検証したところ。
class Test hoge="koko" class_eval "def #{hoge}; p 'hello'; end" end Test.new.koko
■実行結果
hello
eval内に変数を渡したいときはこう。
class Test hoge="koko" class_eval "def #{hoge}(_foo); p 'hoge';p _foo; end" end foo = "bar" Test.new.koko(foo)
■実行結果
"hoge" "bar"
eval内の変数
こうやって書くと、hogeが解決できないとエラーになる。これは分かりやすい。
class Test hoge="koko" class_eval "def #{hoge}(_foo); p hoge; end" end foo = "bar" Test.new.koko(foo)
■実行結果
`koko': undefined local variable or method `hoge' for #<Test:0x2693d90> (NameError)
ここで嵌った。。
分かりにくいのが、変数を#{}で展開してしまったケース
class Test hoge="koko" class_eval "def #{hoge}(_foo); p #{hoge}; end" end foo = "bar" Test.new.koko(foo)
■実行結果
(eval):1:in `koko': wrong number of arguments (0 for 1) (ArgumentError) from (eval):1:in `koko'
これは、hogeを展開してkokoになり、さらにそのkokoを展開しようとしている。
kokoはeval内でメソッドとして定義されているため、
NameErrorとはならずに、引数エラーとなっている。
#引数の形が同じだと、無限ループとなりStackOverFlowになる。
##分かりやすくするために引数を1つとる形でkokoメソッドを定義した。
素朴に"koko"を表示したいときはどうするのか?
ここでシンボルの出番。
class Test hoge="koko" class_eval "def #{hoge}; p :#{hoge}.to_s; end" end Test.new.koko
■実行結果
"koko"
シンボル化することで変数が展開されない。
うーん。なるほど。
【Ruby】【メタプログラミング】言語内DSLの実装。StringScannerを使ったインデントパーサーの実装
■topic summary
study about DSL
この本に載っているDSLをお勉強。
- 作者: まつもとゆきひろ,David Flanagan,卜部昌平(監訳),長尾高弘
- 出版社/メーカー: オライリージャパン
- 発売日: 2009/01/26
- メディア: 大型本
- 購入: 21人 クリック: 356回
- この商品を含むブログ (124件) を見る
出力のインデントは自作。
DSLコード
pagetitle = "xml generate test page" out = "" XML.generate(out) do html do head do title{pagetitle} comment "this is test" end body do h1(style: "font:red",size: "5px"){pagetitle} ul type: "sequre" do li {RUBY_VERSION} li {Time.now.to_s} end end end end XML.show(out)
出力コード
<html> <head> <title> xml generate test page </title> <!-- this is test --> </head> <body> <h1 style='font:red' size='5px'> xml generate test page </h1> <ul type='sequre'> <li> 2.0.0 </li> <li> 2013-11-23 19:38:28 +0900 </li> </ul> </body> </html>
ポイント
instance_evalに渡すブロックの中にさらにブロックが存在
method_missingを利用(第一引数にメソッド名が渡される)
実装
require'strscan' class XML def initialize(out) @out = out end def comment(text) @out << "<!-- #{text} -->" nil end def tag(tagname, attribute={}) @out << "<#{tagname}" attribute.each do |key, val| @out << " #{key}='#{val}'" end @out << ">" if block_given? content = yield @out << content if content end @out << "</#{tagname}>" nil end alias method_missing tag def self.generate(out, &b) XML.new(out).instance_eval(&b) end def self.show(text) result = "" nest_level = 0 s = StringScanner.new(text) while !s.eos? if s.scan(/<[^>]+>/) if s[0].index("</") nest_level -= 1 result << "\t"*nest_level + s[0] + "\n" elsif s[0].index("/>") || s[0].index("<!--") result << "\t"*nest_level + s[0] + "\n" else #開始タグ result << "\t"*nest_level + s[0] + "\n" nest_level += 1 end else s.scan(/[^<]+/) result << "\t"*nest_level + s[0] + "\n" end end print result end end
ふむふむ。
【Ruby】【メタプログラミング】require,load,クラス定義,プログラム終了のトレーシング
■topic summary
study about tracing require, load, define-class, exit-program
ポイント
・at_exitはブロックを登録(再定義しない)
・loadメソッドはファイル名を指定(※1)
※1
(ここでは簡単な例としてライブラリをロードしてみたが、)フルパスを指定して リソースファイルをロードしたりするのがloadのまっとうな使いかたである。
http://www.loveruby.net/ja/rhg/book/load.html
module ClassTrace alias original_require require alias original_load load T = [] def require(file) T << ["require: ", file, caller[0]] original_require(file) end def load(file) T << ["load: ", file, caller[0]] original_load(file) end def Object.inherited(c) T << ["inherited: ", c, caller[0]] end at_exit{T.each {|t| p t}} end include ClassTrace require "bigdecimal" load "test.rb" class Test end
■実行結果
["require: ", "bigdecimal", "C:/Users/HOGE_ADMIN/Desktop/test/arrays.rb:22:in `<main>'"] ["require: ", "rubygems/requirement", "C:/Ruby200/lib/ruby/2.0.0/rubygems/dependency.rb:4:in `<top (required)>'"] ["inherited: ", Gem::Dependency, "C:/Ruby200/lib/ruby/2.0.0/rubygems/dependency.rb:6:in `<top (required)>'"] ["inherited: ", Gem::PathSupport, "C:/Ruby200/lib/ruby/2.0.0/rubygems/path_support.rb:6:in `<top (required)>'"] ["inherited: ", BigDecimal, "C:/Ruby200/lib/ruby/2.0.0/rubygems/core_ext/kernel_require.rb:45:in `require'"] ["load: ", "test.rb", "C:/Users/HOGE_ADMIN/Desktop/test/arrays.rb:23:in `<main>'"] ["require: ", "benchmark", "test.rb:1:in `<top (required)>'"] ["inherited: ", Benchmark::Job, "C:/Ruby200/lib/ruby/2.0.0/benchmark.rb:306:in `<module:Benchmark>'"] ["inherited: ", Benchmark::Report, "C:/Ruby200/lib/ruby/2.0.0/benchmark.rb:344:in `<module:Benchmark>'"] ["inherited: ", Benchmark::Tms, "C:/Ruby200/lib/ruby/2.0.0/benchmark.rb:380:in `<module:Benchmark>'"] ["inherited: ", Test, "C:/Users/HOGE_ADMIN/Desktop/test/arrays.rb:24:in `<main>'"]
ふむふむ。
【Ruby】【メタプログラミング】チェイン(フック)& 特異メソッド を使ったメソッド呼び出しのトレーシング
■topic summary
how to trace method calling with using eigenclass.
委譲とmethod_missingを使ったトレーシングは前にやったけど、
今度はチェイン(フック)を使ったトレーシングを実装します。
ポイント
特異メソッドを使ったトレーシング
・式展開が2レベルに渡る(特異メソッド定義時、実行時)
・インスタンス変数は不要になった時点で削除(@_traced)
・evalで定義するメソッドが、
⇒引数にブロックを持つ場合・・・evalの引数は文字列
⇒ブロックを持たない場合・・・evalの引数はブロックでOK
・特異クラス生成イディオム(1個め、2個めのselfはそれぞれ別オブジェクトを参照)
コード
class Object def trace!(*methods) @_traced = @_traced || [] methods = public_methods(false) if methods.size == 0 methods.map!{|m| m.intern} methods -= @_traced return if methods.empty? @_traced |= methods eigenclass = class << self; self; end STDOUT << "Tracing #{methods.join(",")} on #{object_id}\n" methods.each do |m| eigenclass.class_eval %Q{ def #{m}(*args, &b) STDOUT << "Entering #{m}(\#{args.join(",")})\n" result = super || "nil" STDOUT << "Exiting #{m} with \#{result}\n" result end } end end def untrace!(*methods) if methods.size == 0 methods = @_traced else methods &= @_traced end STDOUT << "Untracing #{methods.join(",")} on #{object_id}\n" methods.map!{|m| m.intern} eigenclass = class << self; self; end eigenclass.class_eval do methods.each do |m| remove_method m end end @_traced -= methods end end b = [1,2,3] b.trace!(:index) b.index("a") b.untrace!(:index) b.index("a")
■実行結果
Tracing index on 22638228 Entering index(a) Exiting index with nil Untracing index on 22638228
ふむふむ。
【Ruby】【メタプログラミング】class_eval と instance_eval
■topic summary
difference between "class_eval" and "instance_eval"
class_eval と instance_eval
class String class_eval("p self") instance_eval("p self") end
■実行結果
String String
コンテキストは同じ。
生成されるメソッドタイプが違う
class String class_eval("def hoge1;end") instance_eval("def hoge2;end") p instance_methods.index(:hoge1) p public_methods.index(:hoge2) end
■実行結果
110 1
class_evalがインスタンスメソッドで
instance_evalがクラスメソッド
前に、attrメソッドを自作しようとして四苦八苦している過程でclass_evalを発見したけど、この実装で大きくずれてないみたい。
・moduleにattrメソッドを実装
・class_evalの実行によるインスタンスメソッド作成
・文字列でのメソッド定義
(自分はヒアドキュメントを使っていたけど、
オライリーでは空文字列 "" にadd(<<)していく形で実装してる)
良かった良かった。