getElementByIdで要素を取得できない



0   名前: うひょ : 2007/01/18(木) 23:40  ID:Tdx04bYt sub-bK
document.getElementByIdを使った要素の取得ができません。
(this.objがnullになっている)
色々と初めてのことをしたので、他の所が悪いのかもしれませんが、
僕には分かりませんでした。よろしくお願いします。
<html><head><title>時計</title>
<script type="text/javascript">
//ClockU objIDは使う要素のID,autostartはtrueならonloadと同時に時計スタート,myRegは書式
//($hhは時,$mmは分,$ssは秒に置換,$$は$に置換される)
function ClockU(objID,autostart,myReg){
	this.cID =null;
	this.mReg = myReg;
	this.objID = objID;
	play = function(){
		this.obj.innerHTML = this.mReg.replace(/[^\$]\$hh/gi,Date.getHours()).replace(/[^\$]\$mm/gi,Date.getMinutes()).replace(/[^\$]\$ss/gi,Date.getSeconds()).replace(/\$\$/gi,"$");
	}
	start = function(){
		this.obj = document.getElementById(this.objID);
		this.cID = setInterval(play,1000);
		return this;
	}		
	stop = function(){
		if(this.cID) clearInterval(this.cID);
		this.cID=null;
		return this;
	}
	
	if(autostart){
		if(document.all)
			attachEvent('onload',start);
		else
			addEventListener('load',start,false);
	}
}
input_obj= new ClockU("input_sample",true,"ただ今$hh時$mm分$ss秒");
span_obj = new ClockU("span_sample" ,true,"ただ今$hh時$mm分$ss秒");
word_obj = new ClockU("word_sample" ,true,"- $hh:$mm:$ss -");
</script>
</head>
<body>
input:<input id="input_sample" value="読み込み中..."><br>
span要素:<span id="span_sample" style="border:2px solid green">読み込み中...</span><br>
div要素↓<div id="word_sample" style="border:2px solid yellow">読み込み中。</div>
<br>
input以外は分かりやすくするためにスタイルシートで枠線をつけています。
</div>
</body></html>

1   名前: 匿名 : 2007/01/18(木) 23:40  ID:Rt.j/.AI sub-kJ
> play = function(){}
> start = function(){}
> stop = function(){}

これでは、play、start、stop がグローバルな無名関数になる。どうせグローバル関数なら、外に出した方がコードの見通しが良い。

また、これらの実行時に、呼び出し元となるオブジェクトが与えられていない。つまり、関数内の this 値は null となる(この場合、this 値はグローバルオブジェクトとなる)。

平たく言えば、play、start、stop 関数実行時の this 値は window である。window.objID を参照しても当然 undefined になる。

JavaScript の this 値は、関数呼び出し時に決まる。定義した位置は全く関係ない。慣れないうちは、this が何を指しているか alert() でも使って逐一確認しる。



インスタンスメソッドとして定義するなら、

this.play = function(){}
this.start = function(){}
this.stop = function(){}

にする。だが、インスタンス生成のたびに(無名)関数を作るのは非効率なので、通常は prototype に登録しておく。

もっとも今回の場合、this 値の保存がかなり面倒だと思うが……。


> if(document.all) attachEvent('onload',start);

document.all → attachEvent は必ずしも成り立たない(cf. Firefox)。なぜか document.all 分岐が好きな人が多いが、ずばり使用したいメソッドを検査すれば良いじゃないか。

if(typeof attachEvent != 'undefined') attachEvent('onload',start);

2   名前: うひょ : 2007/01/18(木) 23:40  ID:Tdx04bYt sub-bK
色々とやってみました。
play = function(){}
start = function(){}
stop = function(){}

this.play = function(){}
this.start = function(){}
this.stop = function(){}
変えましたが、
this.cID = setInterval(this.play,1000);
でエラーが出ました。
また、呼び出し元となるオブジェクトは関数の第一引数にあると聞いたのでarguments[0]としたり
しましたが、やはりダメでした。
prototypeも考えましたが、this値の保存がやはり分かりませんでした。
あと、document.allの件はありがとうございました。
結構新しいページでも使ってたので、良いのかと思いました。
結局、どうすればいいでしょうか…。何度もすみません。

3   名前: うひょ : 2007/01/18(木) 23:40  ID:Tdx04bYt sub-bK
>JavaScript の this 値は、関数呼び出し時に決まる。定義した位置は全く関係ない。
ということをすっかり忘れて、
>this.cID = setInterval(this.play,1000);
>でエラーが出ました。
というような発言をしてしまいました。
すみませんでした。

4   名前: 匿名 : 2007/01/18(木) 23:40  ID:Rt.j/.AI sub-kJ
this 値に関して:

function func () { this; } // この時点では this は何も指さない

var obj = { };
obj.method = func;
obj.method ();        // this 値は obj。

window.func ();       // this 値は window。

func ();              // this 値は null、ゆえに window を指す。

func.call (obj);      // this 値は指定された obj を指す。

setInterval (obj.method, 1000);
    // obj.method が参照しているのは func 関数。
    // ゆえに、タイムアウト時に呼ばれるのは this 値のない func 関数。
    // すなわち、this 値は window。

setInterval (function () { obj.method (); }, 1000);
    // タイムアウト時に呼ばれるのは無名関数であり、
    // その中で obj.method が呼ばれる。
    // すなわち、関数 func が、this 値 obj で呼び出される。


> 呼び出し元となるオブジェクトは関数の第一引数にあると聞いた

?Perl か何かの話?


> this値の保存

this.cID = (function (thisObj) {
               return setInterval (function () { thisObj.play() }, 1000);
            } )(this);


あるいは、絶対に他のオブジェクト(インスタンス)と干渉しない自信があるなら以下でも良い。

var thisObj = this;
this.cID = setInterval (function () { thisObj.play() }, 1000);


同じような this 値の保存問題はイベントリスナ登録時も起こりうるが、こちらの場合はもう少し対処法がある。setTimeout/setInterval については、私は今のところ上記以外の方法が思い付かない。

5   名前: うひょ : 2007/01/18(木) 23:40  ID:Tdx04bYt sub-bK
匿名様、ありがとうございます。
this値の保存をそのまま使ってみると動かなかったので、
匿名様のthis値に関する例を参考に色々改造を施したり(stop()はまだ使用しないため未改造)して最終的に
以下のようになりましたが、まだ成功しません。どうやらsetIntervalが実行されていない気がします。
どうしてでしょうか?やはり改造が悪かったのでしょうか…
<html><head><title>時計</title>
<script type="text/javascript">
//ClockU objIDは使う要素のID,autostartはtrueならonloadと同時に時計スタート,myRegは書式
//($hhは時,$mmは分,$ssは秒に置換,$$は$に置換される)
ClockU_gbl = [];
function ClockU(objID,autostart,myReg){
	this.cID =null;
	this.mReg = myReg;
	this.objID = objID;
	this.play = function(){ (function(thisObj){
		alert(thisObj.mReg);			//ここは出ない
		thisObj.obj.innerHTML = thisObj.mReg.replace(/[^\$]\$hh/gi,Date.getHours()).replace(/[^\$]\$mm/gi,Date.getMinutes()).replace(/[^\$]\$ss/gi,Date.getSeconds()).replace(/\$\$/gi,"$");
		})(this);
	}
	this.start = function(){ function(tthisObj){
		tthisObj.obj = (function (thisObj) {
			return document.getElementById(thisObj.objID);
		})(this);
		tthisObj.cID = (function (thisObj) {
           		return setInterval (function () { thisObj.play() }, 1000);
           	} )(this); }
//		alert();		//ここでアラートは出る
		return this;
	}		
	this.stop = function(){
		if(this.cID) clearInterval(this.cID);
		this.cID=null;
		return this;
	}
	
	if(autostart){
		if(typeof attachEvent != 'undefined')
			attachEvent('onload',this.start);
		else
			addEventListener('load',this.start,false);
	}
}
input_obj= new ClockU("input_sample",true,"ただ今$hh時$mm分$ss秒");
span_obj = new ClockU("span_sample" ,true,"ただ今$hh時$mm分$ss秒");
word_obj = new ClockU("word_sample" ,true,"- $hh:$mm:$ss -");
</script>
</head>
<body>
input:<input id="input_sample" value="読み込み中..."><br>
span要素:<span id="span_sample" style="border:2px solid green">読み込み中...</span><br>
div要素↓<div id="word_sample" style="border:2px solid yellow">読み込み中。</div>
<br>
input以外は分かりやすくするためにスタイルシートで枠線をつけています。
</div>
</body></html>

6   名前: 匿名 : 2007/01/18(木) 23:40  ID:Rt.j/.AI sub-kJ
function ClockU (objID, myReg) {
    this.objID = objID;
    this.mReg  = myReg;
    this.cID   = null;
    this.obj   = null;
}

ClockU.prototype.play = function () {
/*
Date.getHours などのコンストラクタメソッドの所在が不明なので
とりあえずインスタンス生成しておいた。
正規表現より、各時刻用のノードを一度だけ生成した方が楽だと思う。
*/
    var d = new Date;
    this.obj.innerHTML = this.mReg.replace (/\$hh/gi, d.getHours ())
                                  .replace (/\$mm/gi, d.getMinutes ())
                                  .replace (/\$ss/gi, d.getSeconds ());
};

ClockU.prototype.start = function () {
/*
getElementById をこのメソッド内から放逐した方が良いかもしれない。
*/
    this.obj = document.getElementById (this.objID);
    
    this.cID = (function (thisObj) {
                   return setInterval (function () { thisObj.play (); }, 1000);
                } )(this);
};

ClockU.prototype.stop = function () {
    clearInterval (this.cID);
};

ClockU.create = function (objID, autostart, myReg) {
    var obj = new this (objID, myReg);
    
    if (autostart)
/*
メソッドの存在確認は、基本的に一般→特殊の順で行う。
最初に特殊な attachEvent 判定をすると、Opera が引っ掛かる。
そうすると、Opera で DOM Events の柔軟な特性が使いにくくなる。
*/
        if (typeof addEventListener != 'undefined')
            addEventListener ('load', function (e) { obj.start (); }, false);
        else if (typeof attachEvent != 'undefined')
            attachEvent ('onload', function () { obj.start (); } );
        else
            ;
    
    return obj;
};

/*
理由がなければ、変数は必ず var 宣言しておくこと。でないとグローバル変数になる。
まあ、この位置はどの道グローバルなわけだが。
*/
var input_obj = ClockU.create ('input_sample', true, 'ただ今$hh時$mm分$ss秒');
var span_obj  = ClockU.create ('span_sample' , true, 'ただ今$hh時$mm分$ss秒');
var word_obj  = ClockU.create ('word_sample' , true, '- $hh:$mm:$ss -');

7   名前: うひょ : 2007/01/18(木) 23:40  ID:Tdx04bYt sub-bK
匿名様、ありがとうございました。
これをもとに頑張ってみます。
数々のアドバイス、最終的なサンプルの提供、
本当にありがとうございました。

一覧へ戻る