トップ «前の日記(2007-01-13) 最新 次の日記(2007-01-15)» 編集

日々の破片

Subscribe with livedoor Reader
著作一覧

2007-01-14

_ JavaScriptのおもしろさ

前回の続き。(少しずつ足してく)
  1. 表示内容を答えよ。
    function false_check(x) {
        if (x) {
            alert('true');
        } else {
            alert('false');
        }
    }
     
    false_check(false);
    false_check(0);
    false_check(Number(0));
    false_check(new Number(0));
    var x;
    false_check(x);
    false_check(Boolean(false));
    false_check(new Boolean(false));
    false_check(Boolean(new Boolean(false)));
    
  2. 表示内容を答えよ。(先にタネ明かしをしておくと、IEで動かしても何がポイントかさっぱりわからないと思います)
    function substr(s) {
        try {
            alert(s.substring(0, 2));
        } catch (e) {
            alert(e);
        }
    }
    StringX = function(x) { this.value = x; }
    StringX.prototype = "abcdef";
    StringY = function(x) { this.value = x; }
    StringY.prototype = new String("abcdef");
    var x = new StringX("abcdef");
    var y = new StringY("abcdef");
    substr(x);
    substr(y);
    
  3. 表示内容を答えよ
    alert('abc');
    alert(new String('abc'));
    alert('abc'[0]);
    alert(new String('abc')[0]);
    
    (追加:IE7とFirefoxでは表示が異なる。どちらがECMAScriptの仕様に合致するか答えよ)
  4. 表示内容を答えよ
    var x = [1,2,3,4,5]
    var y = x.concat([6]);
    x[0] = 8;
    alert(y);
    x = [1,2,3,4,5]
    y = x.reverse();
    x[0] = 8;
    alert(y);
    x = [1,2,3,4,5]
    y = x.push(6);
    alert(y);
    
  5. 表示されるものを答えよ
    function eval_result(x) {
        try {
            alert(eval(x));
        } catch (e) {
            alert(e);
        }
    }
    eval_result('a');
    eval_result("32 +");
    eval_result(32);
    eval_result(1, 3);
    eval_result(["e", "v", "a", "l"]);
    var s = "eval_result(s)";
    eval_result(s);
    var x = "eval_result(x)";
    eval_result(x);
    eval_result("eval_result(1)");
    
  6. (? 想定外の動作になるので、後でECMAScriptの仕様を確認するかも:追記:「想定外」というのは、『入門JavaScript』の記述から想定した動作と違うという意味です。で、これについては記述が誤っているので、以下の動作はブラウザーの動作で正しいです。どっちにしてもブラウザーの動作は最初の基準になるわけだけど)
    Test = function(x) { this.value = x; }
    Test.prototype.get = function() { return this.value; }
    XTest = function(y) { Test(y); }
    XTest.prototype = new Test(0);
    var x = new XTest(3);
    alert(x.get());
    Test.prototype.sub = function(x) { return this.value - x; }
    alert(x.sub(30));
    Test2 = function(x) { this.value = x; }
    Test2.prototype.get = function() { return this.value; }
    XTest2 = function(y) { Test2(y); }
    XTest2.prototype = Test2.prototype;
    var x = new XTest2(3);
    alert(x.get());
    Test2.prototype.sub = function(x) { return this.value - x; }
    alert(x.sub(30));
    
  7. 上との比較(これは想定内)
    Test = function(x) { this.value = x; }
    Test.prototype.get = function() { return this.value; }
    XTest = function(y) { this.value = y; }
    XTest.prototype = new Test(0);
    var x = new XTest(3);
    alert(x.get());
    Test.prototype.sub = function(x) { return this.value - x; }
    alert(x.sub(30));
    Test2 = function(x) { this.value = x; }
    Test2.prototype.get = function() { return this.value; }
    XTest2 = function(y) { this.value = y; }
    XTest2.prototype = Test2.prototype;
    var x = new XTest2(3);
    alert(x.get());
    Test2.prototype.sub = function(x) { return this.value - x; }
    alert(x.sub(30));
    
  8. 継承したprototypeの場合(上との比較。これも想定内)
    Test = function(x) { this.value = x; }
    Test.prototype.get = function() { return this.value; }
    XTest = function(y) { this.value = y; }
    XTest.prototype = new Test(0);
    var x = new XTest(3);
    alert(x.get());
    XTest.prototype.sub = function(x) { return this.value - x; }
    alert(x.sub(30));
    var x = new Test(3);
    alert(x.sub(30));
    Test2 = function(x) { this.value = x; }
    Test2.prototype.get = function() { return this.value; }
    XTest2 = function(y) { this.value = y; }
    XTest2.prototype = Test2.prototype;
    var x = new XTest2(3);
    alert(x.get());
    XTest2.prototype.sub = function(x) { return this.value - x; }
    alert(x.sub(30));
    var x = new Test2(3);
    alert(x.sub(30));
    
  9. 関数の作成方法
    var add = new Function('a', 'b', 'return a + b;');
    alert(add(5,6));
    var mul = new Function('a,b', 'return a * b;');
    alert(mul(5,6));
    var div = Function('a', 'b', 'return a / b;');
    alert(div(10,2));
    
  10. 表示される内容は?
    var func = function(x) { 
      if (x == 0) { return 1; }
      return x * arguments.callee.call(null, x - 1);
    }
    alert(func.apply(null, [5]));
    

_ JavaScriptを試すためのリソース

    JScript
  • MAS(JScriptコンソールなど)
  • 注意:ErrorオブジェクトのtoStringメソッドの実装があまりにもエンドユーザー志向なので、プログラマはMozilla系のJavaScript実装を利用するほうが良いと思う
    仕様、教科書など
  • JavaScript Language Resources
  • 入門 JavaScript (My UNIX Series)(久野 靖)
    • 入門JavaScript(アスキーのページ:正誤表がない)
      • 勝手に正誤表
      • P.53 表2-5 Math.LOG10EとMath.LOG2Eの説明が正しくない(eが底となる間違ってました(shinhさんの指摘)。10Eと2Eだから、それぞれ10と2が底)
      • P.53 表2-5 演習率→円周率
      • P.65 コンストラクタ内でのコンストラクタ呼び出しは(少なくても現在のFirefoxおよびIEでは)正しく動作しない。したがって自前でプロパティの初期設定が必要と思われる。上のエントリーの設問6参照
      • 追記:良く読み返したら図3-6の表示から、明らかに(この時点で)久野さんは間違えている。add(3)した結果のvalueが3となっているということは、prototypeに設定したnew Counter(0)のvalueプロパティの0が有効になっているということだ。したがってP.65最後の2行は正しくない。コンストラクタExCounter()で、Counter()を関数として呼んでは「ならない」。
      • P.86 正規表現オブジェクトの名前はRegExp

_ わからんなぁ

で、結局次の動作がどうあるべきかはわからない。
Test = function(x) { this.value = x; }
XTest = function(x) { Test(x); }
XTest.prototype = new Test(0);
var x = new XTest(10);
alert(x.value);

想定できる動作は、

  1. new でObjectのインスタンスを作りそれをthisとして
  2. XTest() を呼び
  3. その中でTest()を呼ぶ(thisは、1で作ったオブジェクト)
  4. その後に、XTestのprototypeの既存プロパティをthisに設定(valueプロパティは上書き)
なのだが、本当にそうなのかな? (追記:もちろん間違っています。どこが間違っているか考えてみましょう。本来どう動くかを考えれば良いということです)

Test = function(x) { this.value = x; alert('Test():' + this.value); }
XTest = function(x) { 
  for (var p in this) {
    alert(p + "=" + this[p]);
  }
  Test(x); 
  for (var p in this) {
    alert(p + "=" + this[p]);
  }
}
XTest.prototype = new Test(0);
var x = new XTest(10);
alert(x.value);
for (var p in x) {
  alert(p + '=' + x[p]);
}

実際に動かすと、thisが同一と仮定すると

  1. new でObjectのインスタンスを作りそれをthisとして
  2. XTest() を呼び
  3. XTestのprototypeの既存プロパティをthisに設定して
  4. その中で新たに作ったインスタンスをthisとしてTest()を呼び返ってきたところでそれを廃棄
と動いているように見える。(追記:これも間違い。4.は、グローバル関数のTestを呼んでいるため、Testの中のthisはグローバルオブジェクト(ブラウザーならwindow)になる。したがって「廃棄」もなにもない。malaさんのツッコミ参照

これはどうだ?
Test = function(x) { this.value = x; }
var test = new Test(10);
XTest = function(x) { Test(x); }
XTest.prototype = test;
var xtest = new XTest(11);
var xtest2 = new XTest(12);
alert(test.value);
alert(xtest.value);
alert(xtest2.value);
test.value = 38;
alert(test.value);
alert(xtest.value);
alert(xtest2.value);
xtest.value = 40;
alert(test.value);
alert(xtest.value);
alert(xtest2.value);

む、コンストラクタのスコープで代入しないとプロパティが作られないということのようだ(というか実行コンテキストのthisが別のものになる)。(ECMAScriptのnew expressionの仕様(11.2.2)ではこの部分の詳細な動作は規定されていないように見えるけど、違うところを読んでいるかも——追記:規定されるもなにも定義した通りに動作しているだけ)

結論:オブジェクトによって異なるプロパティ値が必要な場合、コンストラクタから呼び出したコンストラクタ内で定義したプロパティはコンストラクタ内で上書きしなければならない。もしプロパティの初期化処理を共通化したいなら、こうすりゃいいかな。

function setValue(o, v) {
  o.value = v;
}
Test = function(x) { setValue(this, x); }
XTest = function(x) { setValue(this, x); }
XTest.prototype = new Test(0);
var x = new XTest(10);
alert(x.value);

別の言い方をすると、クラス変数に相当するものは、prototypeとして利用するオブジェクトのプロパティを利用すれば良い。と書いたけどそれだけでは間違い。自分をthisとして代入するとその時点で新たなプロパティが作られるので、書き換え可能なクラス変数とはならない(読み込み専用となる)。あくまでもprototypeののプロパティを利用する。

function ctr_in_ctr5() {
  Test = function(x) { this.classvar = x; }
  XTest = function() { ; }
  XTest.prototype = new Test(30);
  XTest.prototype.cvarset = function(x) { XTest.prototype.classvar = x; }
  XTest.prototype.cvarget = function() { return XTest.prototype.classvar; }
  var x = new XTest();
  alert(x.cvarget());
  var y = new XTest();
  alert(y.cvarget());
  x.cvarset(31);
  alert(x.cvarget());
  alert(y.cvarget());
}
本日のツッコミ(全2件) [ツッコミを入れる]
_ mala (2007-01-14 17:47)

> 3. その中でTest()を呼ぶ(thisは、1で作ったオブジェクト)<br>この3が間違ってます。<br>XTest(10)でも、new XTest(10)でも、ブラウザで実行してるなら、window.value = 10になります。<br>ECMAScript的にも、この挙動であってます。<br><br>Test = function(x) { this.value = x; }<br>XTest = function(x) { Test.apply(this,arguments); }<br>x = new XTest(10)<br><br>これだとx.value = 10になります。

_ arton (2007-01-14 20:43)

ああ、なるほど。ありがとうございます。<br>TextもXTestもグローバルオブジェクトの中で定義されているから、明示的なnewの直後の呼び出しのXTestについてはthisは新しく作られたオブジェクトだけど、Test自体にはthisは与えてないということですね。


2003|06|07|08|09|10|11|12|
2004|01|02|03|04|05|06|07|08|09|10|11|12|
2005|01|02|03|04|05|06|07|08|09|10|11|12|
2006|01|02|03|04|05|06|07|08|09|10|11|12|
2007|01|02|03|04|05|06|07|08|09|10|11|12|
2008|01|02|03|04|05|06|07|08|09|10|11|12|
2009|01|02|03|04|05|06|07|08|09|10|11|12|
2010|01|02|03|04|05|06|07|08|09|10|11|12|
2011|01|02|03|04|05|06|07|08|09|10|11|12|
2012|01|02|03|04|05|06|07|08|09|10|11|12|
2013|01|02|03|04|05|06|07|08|09|10|11|12|
2014|01|02|03|04|05|06|07|08|09|10|11|12|
2015|01|02|03|04|05|06|07|08|09|10|11|12|
2016|01|02|03|04|05|

ジェズイットを見習え