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

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


関連コンテンツ

スポンサーリンク

「JavaScriptの無名関数とクラス」への12件のフィードバック

  1. >ここに、その書き込みの意図は?

    こういう事に使いたい人に注目されるエントリかな~と思って。

  2. babu_babu_babooさん

    調べてみてもイマイチピンとこないのは
    完全に私のレベルの問題です(^_^;)

    お二方のお蔭で、作りたかったページを
    構想通りに作ることができました。

    私の文章力では上手く表現できませんが
    お二方には本当に感謝しています。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください