Fork me on GitHub

2007/11/04

Recent entries from same category

  1. JScript.NETでスレッド Hatena
  2. javascriptで動くtwitter streamクライアントを作るならばmultipart/mixedを使うべき Hatena
  3. バッチファイルで簡易Webサーバを起動する。 Hatena
  4. twitter.bat Hatena
  5. XSLTを使ったAmazon最速検索が動くようになった。 Hatena

はてな
そうなのか...知らなかった。

The Future of JavaScript
var a = [1,2,3]; var a2 = a.pop; var b = a2() は?
  this のコンテキストが変わってるのでエラーになる
なんでだろ。 メソッド呼び出し時に正しくコンテキストスイッチしてないって事?

例えば
var obj = new(function Clazz() {
  var self = this;
  self.value = 0;
  self.plus = function() { self.value++; };
})();
var func_plus = obj.plus;
func_plus();
alert(obj.value);
なら結果は「1」になるよね?それってもしかして
var obj = new(function Clazz() {
  this.value = 0;
  this.plus = function() { this.value++; };
})();
var func_plus = obj.plus;
func_plus();
alert(obj.value);
こうなってるって事じゃないの?うむ...それっていいの?
コンテキスト保持しておけば
function Clazz() {
  var self = this;
  self.value = 0;
  self.plus = function() { self.value++; };
}
var obj1 = new Clazz();
var obj2 = new Clazz();
var func_plus = obj1.plus;
func_plus();
obj2.plus = func_plus;
obj2.plus();
alert(obj1.value + "," + obj2.value);
こんな事しても、例外出ずに動くよね?obj1.valueがインクリメントされる結果になるけど...

これってperlなら
#!/usr/bin/perl

use strict;
use warnings;

package Clazz;
sub new {
  my $class = shift;
  my $self = { value => 0 };
  return bless $self, $class;
}
sub plus {
  my $self = shift;
  ++$self->{value};
  1;
}

my $obj1 = Clazz->new;
my $obj2 = Clazz->new;

my $func_plus = *Clazz::plus;
warn "func_plus=".$func_plus."\n";
$func_plus->($obj1);
warn sprintf "obj1->{value}=%d, obj2->{value}=%d\n",
    $obj1->{value}, $obj2->{value};

$obj2->{plus} = $func_plus;
$obj2->plus();
warn sprintf "obj1->{value}=%d, obj2->{value}=%d\n",
    $obj1->{value}, $obj2->{value};
こんなサンプルかな。実行結果は
func_plus=*Clazz::plus
obj1->{value}=1, obj2->{value}=0
obj1->{value}=1, obj2->{value}=1
サブルーチンにはselfを渡すのはインスタンス自身になるから問題無く動くね。javascriptのようにfunctionの参照自身がインスタンス参照を保持しちゃってる訳じゃなので問題は発生しないか...

pythonなら
class Clazz:
  def __init__(self):
    self.value = 0

  def plus(self):
    self.value = self.value + 1

obj1 = Clazz()
obj2 = Clazz()

func_plus = obj1.plus
print "func_plus=%s" % func_plus
func_plus()
print "obj1.value=%d, obj2.value=%d" % (obj1.value, obj2.value)

obj2.plus = func_plus
obj2.plus()
print "obj1.value=%d, obj2.value=%d" % (obj1.value, obj2.value)
こんなサンプルかな?実行結果は
func_plus=<bound method Clazz.plus of <__main__.Clazz instance at 0xb7ef828c>>
obj1.value=1, obj2.value=0
obj1.value=2, obj2.value=0
pythonの場合は関数の参照自身がインスタンスの参照も保持しているから、いくらobj2.plusを上書きしてもobj2.valueが更新される訳じゃない。

rubyなら
class Clazz
    def initialize() @value = 0 end
    def plus() @value += 1 end
    attr_accessor :value
end

obj1 = Clazz::new
obj2 = Clazz::new
func_plus = Clazz.instance_method :plus
puts "func_plus=%s" % func_plus
func_plus.bind(obj1).call
printf("obj1.value=%d, obj2.value=%d\n", obj1.value, obj2.value)
func_plus.bind(obj2).call
printf("obj1.value=%d, obj2.value=%d\n", obj1.value, obj2.value)
こんな感じ?実行結果は
func_plus=#<UnboundMethod: Clazz#plus>
obj1.value=1, obj2.value=0
obj1.value=1, obj2.value=1
rubyの場合はperlぽいけど、UnboundMethodの呼び出しはまずインスタンスをbindしてから呼ぶ事になるので問題は発生しない。ちなみにobj1.plusを取得してobj2.plusに代入できないかやってみたけど、私の力足らずなのか出来なかった。

luaなら
function Clazz()
  return {
    value = 0,
    plus = function(self)
      self.value = self.value + 1
    end
  }
end
local obj1 = Clazz()
local obj2 = Clazz()
local func_plus = obj1.plus
print("func_plus", func_plus)
func_plus(obj1)
func_plus(obj2)
print("obj1.value=", obj1.value)
print("obj2.value=", obj2.value)
こんな感じ?実行結果は
func_plus   function: 0x8f36270
obj1.value= 1
obj2.value= 1
luaも結局selfを渡す事になるので、問題は派生しない。
ちなみにC++なら
#include <stdio.h>

class Clazz {
private:
  int _value;
public:
  Clazz() : _value(0) {}
  void plus() { _value++; }
  int value() { return _value; }
};

int
main(int argc, char* argv[])
{
    Clazz* obj1 = new Clazz();
    Clazz* obj2 = new Clazz();

    typedef void (Clazz::*def_func_plus)();
    def_func_plus func_plus = &Clazz::plus;
    printf("func_plus=%p\n", func_plus);
    (obj1->*func_plus)();
    printf("obj1->value=%d, obj2->value=%d\n",
            obj1->value(), obj2->value());

    // obj2->plus = func_plus; # compile error

    return 0;
}
こんな感じ?実行結果は
func_plus=0x8048574
obj1->value=1, obj2->value=0
まぁ、無茶すればobj2->plusも置き換えられない事はないけど、結果は見えてるからいいでしょ...

つまり、著名な言語で問題が発生しているのはjavascriptだけって事か...
それって...

まずくない?

blog comments powered by Disqus
WriteBacks

大元の文脈がよくわからんのでなんですが…

>this のコンテキストが変わってるのでエラーになる
a2 が Global(ブラウザなら window) のメソッドとみなされるだけで、エラーにはならんはずですが…

>(二番目の例)こうなってるって事じゃないの?うむ...それっていいの?
いいんです。func_plus は Global のメソッドになって、this は Global の value を指しますから。
メソッドというか関数内の this は実行時の環境において何を参照するか動的に決まります。インスタンスを指すとは限りません。

this の参照先を明示したいなら、apply か call を使って、
func_plus.apply(obj)
alert(obj.value) // 1
ruby みたいですが、こうなります。

大元の話は ES4 の話ですが、この辺は基本的に変わらないはず。class を使った場合は this は常にインスタンスを参照するようになるはずです(AS3 を継承するなら)。
bug fix としても 関数内で関数定義された場合に内側の this が Global を指しちゃうのをきちんと動的にするって話のはず、自信ないですが。

> つまり、著名な言語で問題が発生しているのはjavascriptだけって事か...それって...まずくない?
悪いとこでもあるんですが、いいとこであることもあるので(ぉ

Posted by mal_blue at 2007/11/08 (Thu) 20:21:31

Re:

こんにちわ。コメントありがとうございます。

>大元の文脈がよくわからんのでなんですが…

まず第一に、私もそれがあるのです。

>>this のコンテキストが変わってるのでエラーになる
>a2 が Global(ブラウザなら window) のメソッドとみなされるだけで、エラーにはならんはずですが…

おそらく、スコープ内の話です。ブラウザのアドレスバーに

javascript:(function(){var a=[1,2,3];var a2=a.pop;var b=a2();alert(a);})();void(0);

って打って頂いたらalertが出ないのが確認出来るかと思います。ちなみに

javascript:var a=[1,2,3];a2=a.pop;var b=a2();alert(a);void(0);

でもalertは出ません。

>>(二番目の例)こうなってるって事じゃないの?うむ...それっていいの?
>いいんです。func_plus は Global のメソッドになって、this は Global の value を指しますから。
>メソッドというか関数内の this は実行時の環境において何を参照するか動的に決まります。インスタンスを指すとは限りません。
>
>this の参照先を明示したいなら、apply か call を使って、
>func_plus.apply(obj)
>alert(obj.value) // 1
>ruby みたいですが、こうなります。

はい。動的に変わっているのも知っています。例えばクラスメソッドをタイマーハンドラにしてしまうと、thisはwindowになってしまうのも知っています。
ですので、Arrayのpop自身がthisを見ずに、selfを見て処理すべきなんじゃ...という事を書いているつもりです。
すみません。読み取りにくいですね。

>大元の話は ES4 の話ですが、この辺は基本的に変わらないはず。class を使った場合は this は常にインスタンスを参照するようになるはずです(AS3 を継承するなら)。
>bug fix としても 関数内で関数定義された場合に内側の this が Global を指しちゃうのをきちんと動的にするって話のはず、自信ないですが。

はい。これ(今始まった事ではない事)も認識しています。ただ、いままで私は「b=a2()」で"エラーには"ならないと思っていたので今回他の言語の挙動も調べて見たくなったんです。

>> つまり、著名な言語で問題が発生しているのはjavascriptだけって事か...それって...まずくない?
>悪いとこでもあるんですが、いいとこであることもあるので(ぉ

実際、javascriptの関数参照はインスタンス参照も保持している(func_plusはclazzインスタンスのメンバを指し手いる)にも関わらず、呼び出せないという事に疑問を感じました。
おそらく、popは呼び出せているのだと思うのですが、popが実際にArray自身の配列にアクセスしようとする際、thisを見にいってるんじゃ...と思った訳です。
これがthisでなく、selfで処理していればエラーとならなかったんじゃ...という文章のつもりです。

すみません。読みづらいですね。苦笑

Posted by mattn at 2007/11/09 (Fri) 03:03:23

Re:

どうもです。釈迦に説法だったようで申し訳ない。

self 変数に this 参照を保存しておけば問題ないのはそのとおりなんですけど、元の話は Resig vs amachang のはずなんで、そーゆーことではないんだろうと思いまして。

> 「b=a2()」で"エラーには"ならないと思っていたので今回他の言語の挙動も調べて見たくなったんです。
> おそらく、popは呼び出せているのだと思うのですが、popが実際にArray自身の配列にアクセスしようとする際、thisを見にいってるんじゃ...と思った訳です。

考えられることは、a2 呼び出しの実体は、
Array.prototype.pop.apply(this)
であり、this が Global か環境内の this を指すんだけど、ES4 だと this が pop に対応する型(Array) ではないのでエラー、とかそんな話だったんじゃないかとも思えます。

実際のところは本人(amachang,Resig) に聞かないとわからないので聞いてみますわ…

Posted by mal at 2007/11/09 (Fri) 22:54:23

TrackBack ping me at
Post a comment

writeback message: Ready to post a comment.