広告

posted by fanblog
「ここにブログの名前を入れます」は更新を終了しました。記事はすべて新ブログ「Big Bang」に移転済みです。記事のタイトルをクリックすると新ブログの該当記事に移動します。そちらでお楽しみください。

JavaScriptの無名関数とクラス

2013年5月3日追記
この記事の反省をふまえた続編もあります。この記事で私の書き換えたソースは、非常に無駄な事をしていますので、この記事だけだと恥ずかしいから、そちらもお読み頂ければ幸いです。

なんでもかんでもオブジェクト化すればいいってもんじゃないな。反省!

経過時間を計算してくれるタイマー

マスクドライダー17号さんが、彼のブログで面白いことをやっていて、最初にそのページを開いてからカウントダウンをはじめて、24時間経過したかどうか、残り時間を教えてくれるというスクリプトを走らせています。

その記事によると「教えて!gooにて回答されていた、babu_babooさんのScriptを参考にしました」ということで、babu_babu_babooさんのスクリプトをアレンジしたそうです。

教えて!gooの内容は、期限が来たかどうかカウントしたいだけなので、そのスクリプトは必要な目的を達成しています。

現在、マスクドライダー17号さんが使っているスクリプトは、おそらく24時間で正しく止まると思います。(止まるという表現は正確でないですがわかりやすく言っています)ただ、それを彼のブログで使う場合は不足している機能があります。それは、リセット機能です。

クッキーにデータが残っている

そのスクリプトは、クッキー(cookie)を使って、実現されています。ブラウザを閉じても、最初の時間をクッキーが記憶しているので、もう一度ブログにアクセスした時に、最初の時間からの残りを計算できます。そして、残り時間がなくなると「24時間以上経過しました!!」と表示して終了します。

カウントダウンが1度だけなら、今のままで、まったく問題ないのですが、マスクドライダー17号さんの場合は、そのタイマーを24時間経過する度に、リセットして使いまわしたいのだと思います。でも、今のままだと、最初の24時間が過ぎると「24時間以上経過しました!!」と表示されて、そのまま動かなくなります。その後、もう一度使おうとしても、24時間たってしまったので動きません。

繰り返しますが、教えて!gooの場合は、これで目的を達成出来ているので問題ありません。

現在、マスクドライダー17号さんは、クッキーの有効期限を10日にしているので、止まってから、さらに9日経てば、またスクリプトは使えるようになりますが、かといって、有効期限を短くすると、ブログを開いたときにスクリプトがリセットされてしまい、24時間経ったのかどうか、わからなくなります。ですから「24時間以上経過しました!!」という表示を読むまでは、リセットしたくありません。読み終わったら、ボタンを押して、リセット(カウントダウンの再スタート)をさせるのが、いちばんいいと思います。

元のソースコード

出来れば、動いている今のソースコードをそのまま流用したいです。ありがたいことに、機能ごとに、細かく関数に分けてくれています。この中のsetCookie関数を使えばクッキーはリセット出来ます。

<style type="text/css">
p#mess{ font-size:200%; }
</style>

<p id="mess">次回</p>

<script type="text/javascript">
<!--
(function (doc) {

 function addDay ( day, date ) {
  if( 'number' !== typeof day ) day = 0;
  if( 'object' !== typeof date ) date = new Date;
  date.setDate( date.getDate() + day );
  return date;
 }

 function getCookie ( name ) {
  name = encodeURIComponent( name ).replace( /([.*()]) /g, '\\$1' );
  var value = doc.cookie.match( RegExp( name + '\\s*=\\s*(.*?)(?:[\\s;,]|$)' ) );
  return value ? decodeURIComponent( value[1] ): '';
 }

 function setCookie ( name, value, day, path, domain ) {
  return doc.cookie = encodeURIComponent (name) + '=' + encodeURIComponent (value) +
   '; ' + 'expires=' + ( addDay(day) ).toUTCString () + '; ' + (path ? 'path=' + encodeURI (path) + '; ': '') +
   (domain ? 'domain=' + encodeURI (domain) + '; ': '');
 }
 function padding ( n ) { return n < 10 ? '0' + n: n; }
  var COOKIE_NAME = 'myCount';
 var LIMIT_DAY = 1;
 var SHELF_LIFE = 10;
 var TIMEOUT_MESS = '24時間以上経過しました!!';
 var node = doc.getElementById( 'mess' );
 var targetDay = parseInt( getCookie( COOKIE_NAME ) );

 if( !targetDay ) {
   targetDay = addDay( LIMIT_DAY ).getTime();
   setCookie( COOKIE_NAME, targetDay + '', SHELF_LIFE );
 }

 (function () {
  var text = '次回まで約';
  var s = (targetDay - (new Date).getTime()) / 1000 |0;

  if (s < 0)
   text = TIMEOUT_MESS;

  else {
   text += padding( s % 86400 / 3600 |0) + '時間' +
    padding( s % 3600 / 60 |0) + '分' +
    padding( s % 60 |0) + '秒です。';
   setTimeout (arguments.callee, 1000);
  }

  node.firstChild.nodeValue = text;
 })();
})(this.document);

//-->
</script>

しかし、弱ったことにソースは無名関数で括られているので、setCookie関数を使えません。無名関数の外に出してもいいのですが、この際、オブジェクト(クラス)化して、無名関数内のfunctionを、クラスのprototypeに移動させる事にします。

オブジェクト指向で書き換えたコード

関数の中身は、ほとんどいじっていませんし、スクリプトのロジックは一緒です。

<style type="text/css">
p#mess{ font-size:200%; }
</style>

<p id="mess"></p>
タイマーを<input type="button" onclick="countdown.reset();" value="リセット" />

<script type="text/javascript">
<!--
var timerCookie=function(id){
 this.id=id;
 this.COOKIE_NAME = 'myCount'+id;
 this.LIMIT_DAY = 1;
 this.SHELF_LIFE = 10;
 this.TIMEOUT_MESS = '24時間以上経過しました!!';
 this.targetDay = parseInt( this.getCookie( this.COOKIE_NAME ) );

 if( !this.targetDay ) {
   this.reset();
 }else{
   this.load();
 }
};

timerCookie.prototype.addDay=function( day, date ){
  if( 'number' !== typeof day ) day = 0;
  if( 'object' !== typeof date ) date = new Date;
  date.setDate( date.getDate() + day );
  return date;
};

timerCookie.prototype.getCookie=function( name ){
  name = encodeURIComponent( name ).replace( /([.*()]) /g, '\\$1' );
  var value = document.cookie.match( RegExp( name + '\\s*=\\s*(.*?)(?:[\\s;,]|$)' ) );
  return value ? decodeURIComponent( value[1] ): '';
};

timerCookie.prototype.setCookie=function( name, value, day, path, domain ){
  return document.cookie = encodeURIComponent (name) + '=' + encodeURIComponent (value) +
   '; ' + 'expires=' + ( this.addDay(day) ).toUTCString () + '; ' + (path ? 'path=' + encodeURI (path) + '; ': '') +
   (domain ? 'domain=' + encodeURI (domain) + '; ': '');
};

timerCookie.prototype.padding=function( n ){ return n < 10 ? '0' + n: n; }

timerCookie.prototype.reset=function(){
   this.targetDay = this.addDay( this.LIMIT_DAY ).getTime();
   this.setCookie( this.COOKIE_NAME, this.targetDay + '', this.SHELF_LIFE );
   this.load();
};

timerCookie.prototype.load=function () {
  var text = '次回まで約';
  var s = (this.targetDay - (new Date).getTime()) / 1000 |0;

  if (s < 0)
   text = this.TIMEOUT_MESS;

  else {
   text += this.padding( s % 86400 / 3600 |0) + '時間' +
    this.padding( s % 3600 / 60 |0) + '分' +
    this.padding( s % 60 |0) + '秒です。';
   (function(obj){
	setTimeout (function(){ obj.load(); }, 1000);
   })(this);
  }

  document.getElementById( this.id ).innerHTML = text;
};

var countdown=new timerCookie('mess');
//-->
</script>

これで、setCookie関数を使えるようになりました。setCookie関数を使って作ったのが、上のprototypeにあるreset関数です。元のコードは本来、例文として作られたものですし、1回しか使わないという前提に立ったものです。そういう場合は無名関数で括ってしまうのが便利ですし、使いまわす時はオブジェクト化が便利だと思います。

ちなみに、オブジェクトは表示領域のidを引数にとるようにしています。これで、同じページで時間の違う、複数のタイマーを走らせることも出来ます。

タイマーを

上のソースコードは目的どおりに動くことを24時間たって確認しました。機能的には今のまま使って問題ありません。

ただ、babu_babu_babooさんから、上のソースについていくつかご指摘を頂きました。その内容は、ここのコメント欄にありますが次のような事です。それに沿って、上のソースを修正しようかと思ったのですが、元のソースを残しておかないと、指摘された内容が分からなくなると思うので、このままにしておきます。

指摘された内容
オブジェクト指向なら、始まりは大文字で始めるのが通例
オブジェクトを作るときに、new を使わずに、var obj = TimeCookie.create ();のような形にした方がよい
prototypeに入れるべき関数とは?
2013年5月3日追記
この記事の反省をふまえた続編もあります。この記事で私の書き換えたソースは、非常に無駄な事をしていますので、この記事だけだと恥ずかしいから、そちらもお読み頂ければ幸いです。

なんでもかんでもオブジェクト化すればいいってもんじゃないな。反省!

新ブログ「Big Bang」で続きを読む