

特定の時間だけ販売する商品にカウントダウンタイマーをつけたいと考えています。  
商品を販売開始するまでの時間をカウントするタイマーAと販売を開始してから終了するまでの時間をカウントするタイマーBを用意したいです。  
同じコードを同一ページ内で記載したところタイマーAのカウントダウンが表示されませんでした。  
どの部分を直せば二つのタイマーを同時に動かすことができるのか教えていただきたいです。

[code]

    <html>
    <head>
    <meta charset="utf-8">
    <title>カウントダウンタイマー</title>
    <style>
    body {
      margin: 0;
      padding: 0;
    }
    #inner {
    /*  width: 600px;*/
      margin: 0;
    }
    
    #countdown {
      font-family: arial;
      font-weight: bold;
    /*  width: 600px;*/
    /*  color: #990000;*/
    }
    .day, .hou, .min, .sec, .mil {/*カウントする数字の大きさ*/
      font-size: 48px;
    }
    .day {
    }
    .hou {
    }
    .min {
    }
    .sec {
    }
    .mil {
    }
    .moji {/*日とか時間とか秒とかの文字の色と大きさ*/
      font-size: 36px;
      color:#000000;}
    #text {
      font-family: arial;
      font-weight: bold;  
      font-size: 36px;  
    }
    .box1 {
        padding: 0.5em 1em;
        margin: 0.5em 0;
        font-weight: bold;
        border: solid 3px #000000;
        width: 660px;
    }
    .box1 p {
        margin: 0; 
        padding: 0;
    }
    #inner2 {
    /*  width: 600px;*/
      margin: 0;
    }
    
    #countdown2 {
      font-family: arial;
      font-weight: bold;
    /*  width: 600px;*/
    /*  color: #990000;*/
    }
    #text2 {
      font-family: arial;
      font-weight: bold;  
      font-size: 36px;  
    }
    </style>
    
    <script language="JavaScript" type="text/javascript">
    function CountdownTimer(elm,tl,mes){
     this.initialize.apply(this,arguments);
    }
    CountdownTimer.prototype={
     initialize:function(elm,tl,mes) {
      this.elem = document.getElementById(elm);
      this.tl = tl;
      this.mes = mes;
     },countDown:function(){
      var timer='';
      var today=new Date();
      var day=Math.floor((this.tl-today)/(24*60*60*1000));
      var hou=Math.floor(((this.tl-today)%(24*60*60*1000))/(60*60*1000));
      var min=Math.floor(((this.tl-today)%(24*60*60*1000))/(60*1000))%60;
      var sec=Math.floor(((this.tl-today)%(24*60*60*1000))/1000)%60%60;
      var mil=Math.floor(((this.tl-today)%(24*60*60*1000))/10)%100;
      var me=this;
    
      if( ( this.tl - today ) > 0 ){
       if (day) timer += '<span class="day">'+day+'</span><span class="moji">日</span>';
       if (hou) timer += '<span class="hou">'+hou+'</span><span class="moji">時間</span>';
       timer += '<span class="min">'+this.addZero(min)+'</span><span class="moji">分</span><span class="sec">'+this.addZero(sec)+'</span><span class="moji">秒</span><span class="mil">'+this.addZero(mil)+'</span>';
       this.elem.innerHTML = timer;
       tid = setTimeout( function(){me.countDown();},10 );
      }else{
       this.elem.innerHTML = this.mes;
       return;
      }
     },addZero:function(num){ return ('0'+num).slice(-2); }
    }
    function countdown(){
     var tl = new Date('2019/12/14 20:00:00');
     //この上の部分で終了時間を設定するYO！
     var timer = new CountdownTimer('countdown',tl,'販売開始しました');
     //この上の文は終了した後に表示する文字!
     timer.countDown();
    }
    window.onload=function(){
     countdown();
    }
    </script>
    
    <script language="JavaScript" type="text/javascript">
    function CountdownTimer(elm,tl,mes){
     this.initialize.apply(this,arguments);
    }
    CountdownTimer.prototype={
     initialize:function(elm,tl,mes) {
      this.elem = document.getElementById(elm);
      this.tl = tl;
      this.mes = mes;
     },countDown2:function(){
      var timer='';
      var today=new Date();
      var day=Math.floor((this.tl-today)/(24*60*60*1000));
      var hou=Math.floor(((this.tl-today)%(24*60*60*1000))/(60*60*1000));
      var min=Math.floor(((this.tl-today)%(24*60*60*1000))/(60*1000))%60;
      var sec=Math.floor(((this.tl-today)%(24*60*60*1000))/1000)%60%60;
      var mil=Math.floor(((this.tl-today)%(24*60*60*1000))/10)%100;
      var me=this;
    
      if( ( this.tl - today ) > 0 ){
       if (day) timer += '<span class="day">'+day+'</span><span class="moji">日</span>';
       if (hou) timer += '<span class="hou">'+hou+'</span><span class="moji">時間</span>';
       timer += '<span class="min">'+this.addZero(min)+'</span><span class="moji">分</span><span class="sec">'+this.addZero(sec)+'</span><span class="moji">秒</span><span class="mil">'+this.addZero(mil)+'</span>';
       this.elem.innerHTML = timer;
       tid = setTimeout( function(){me.countDown2();},10 );
      }else{
       this.elem.innerHTML = this.mes;
       return;
      }
     },addZero:function(num){ return ('0'+num).slice(-2); }
    }
    function countdown2(){
     var tl = new Date('2019/12/24 20:00:00');
     //この上の部分で終了時間を設定するYO！
     var timer = new CountdownTimer('countdown2',tl,'販売開始しました');
     //この上の文は終了した後に表示する文字!
     timer.countDown2();
    }
    window.onload=function(){
     countdown2();
    }
    </script>
    
    
    <script type="text/javascript">
    var startday =[];
    var endday = [];
    
    startday[1] = new Date('2019/11/1 20:00');
    endday[1] = new Date('2019/12/14 19:59');
    startday[2] = new Date('2019/11/4 20:00');
    endday[2] = new Date('2019/12/24 23:59');
    var today = new Date();
    
    for(i=1;i<=11;i++){
    if(startday[i] > today || today > endday[i] ){
    $('#time'+i).remove();
    }
    }
    </script>
    </head>
    
    <body>
    
    <div id="time1" class="times">
    <div class="box1">
    <div id="inner">
    <div id="text" style="float: left;">販売開始まで</div><div id="countdown" style="float: left;"></div>
    <div style="clear: both;"></div>
    </div>
    </div>
    </div>
    
    <div id="time2" class="times">
    <div class="box1">
    <div id="inner2">
    <div id="text2" style="float: left;">販売終了まで</div><div id="countdown2" style="float: left;"></div>
    <div style="clear: both;"></div>
    </div>
    </div>
    </div>
    
    
    </body>
    </html>
[/code]


CC BY-SA 4.0
2019-11-30T08:03:16.440
60961
2019-12-01T03:32:09.173
2019-11-30T08:56:28.383
7347
23918
question
javascript
ふたつのカウントダウンタイマーを同一ページ内で表示させたい


二つの問題があります。

  1. 関数`CountdownTimer`が同じグローバル変数であるため、二番目の所で上書きされてしまう。
  2. `windows.onload = function(){...}`で設定できるイベント発生時の関数は一つだけのため、二番目の所で上書きされてしまう。

それぞれ、説明と対策を書いていきます。

**グローバル変数の上書き**

(後述のモジュールを使った場合を除き)HTML内でscriptタグを複数書いたとしても、一つの大きなJavaScriptとみなされて処理され、トップはグローバルの名前空間になります。これはインラインでも外部のJavaScriptを読み込んだ場合も同じです。なんらかの関数の内側ではないトップで関数宣言やvarによる変数宣言を行った場合、その関数名や変数名でグローバル変数が設定されたことになります。そして、このグローバル変数は別のタグ内や別のファイルでも有効であり、また、関数宣言やvarにより上書きすることも可能です。これは、
**グローバル変数汚染** と言われ、昔から問題になっていました。

質問のコードでは両方のscriptタグで`CountdownTimer`を宣言していますが、一番目のscriptタグで書いた関数は二番目のscriptタグで書かれた別の関数で上書きされてしまうと言うことです。一番目に書かれたコードで各処理が実行される際に、既に上書きされてしまった`CountdownTimer`を見に行ってしまうため、一番目のscriptタグの内容は正しく動作することができなくなってしまいました。

グローバル変数汚染を防ぐ方法で最も一般的なのは[IIFE(日本語では即時関数)](https://developer.mozilla.org/ja/docs/Glossary/IIFE)と言われる仕組みを使うことです。トップで関数や変数を宣言するとグローバル変数になってしまいますが、関数内であれば、その関数名や変数名はその関数内でのみ有効なローカル変数になり、グローバル変数を汚染しません。つまり、別々の関数内で宣言されていれば、同じ名前であっても上書き等がされると言うことが無いと言うことです。

IIFEのやり方はとっても簡単。全体を`(function(){`と`})();`で囲むだけです(他にもいくつかバリエーションがあります)。基本的にIIFEは常に必須であり、ほとんどのJavaScriptもIIFEになっています。

なお、ES2015以降はモジュールという仕組みが使えるようになったため、IIFEの代わりにモジュールを使って名前空間をわけるという方法もあります(モジュールの場合は、トップがモジュールの名前空間となり、グローバル変数にはならなくなります)。モジュールはscriptタグで`type="module"`と書くだけで使えるのですが、そのまま使った場合は最新のモダンブラウザでしか対応していないため、詳しい解説は省かせていただきます。。

**`オブジェクト.onイベント名 = 関数`で設定できる関数は一つのみ**

`window`やDOMに対して、あるイベント発生時に関数を実行するようにしたい場合、`on関数名`というプロパティに代入する方法がありますが、この方法で設定できる関数は一つのみです。二回設定しようとすると、設定上書きされ、一回目の関数は呼ばれず、二回目の関数だけが呼ばれるようになります。

質問のコードは、`windows.onload`に対して、一番目のscriptタグで代入した後、二番目のscriptタグでも代入しています。これでは、一番目に代入した関数は忘れ去れ、二番目に代入した関数しか有効になりません。そのため、一番目については全く動作しないという結果になります。

二つ以上の関数をイベントに紐付けたい場合は、`onイベント名`への代入では無く[`addEventListener`](https://developer.mozilla.org/ja/docs/Web/API/EventTarget/addEventListener)を使います。`addEventListener`はイベント時に発行する関数を追加していく形式のため、前回登録した関数が消えることはありません。ただし、`onイベント名`と混ぜて使うとおかしな事になる場合があるため、基本的にはどちらかを使用するようにしてください。

以上を踏まえて修正すると下記のようになります。

[code]

    <html>
    <head>
    <meta charset="utf-8">
    <title>カウントダウンタイマー</title>
    <style>
    body {
      margin: 0;
      padding: 0;
    }
    #inner {
    /*  width: 600px;*/
      margin: 0;
    }
    
    #countdown {
      font-family: arial;
      font-weight: bold;
    /*  width: 600px;*/
    /*  color: #990000;*/
    }
    .day, .hou, .min, .sec, .mil {/*カウントする数字の大きさ*/
      font-size: 48px;
    }
    .day {
    }
    .hou {
    }
    .min {
    }
    .sec {
    }
    .mil {
    }
    .moji {/*日とか時間とか秒とかの文字の色と大きさ*/
      font-size: 36px;
      color:#000000;}
    #text {
      font-family: arial;
      font-weight: bold;  
      font-size: 36px;  
    }
    .box1 {
        padding: 0.5em 1em;
        margin: 0.5em 0;
        font-weight: bold;
        border: solid 3px #000000;
        width: 660px;
    }
    .box1 p {
        margin: 0; 
        padding: 0;
    }
    #inner2 {
    /*  width: 600px;*/
      margin: 0;
    }
    
    #countdown2 {
      font-family: arial;
      font-weight: bold;
    /*  width: 600px;*/
    /*  color: #990000;*/
    }
    #text2 {
      font-family: arial;
      font-weight: bold;  
      font-size: 36px;  
    }
    </style>
    
    <script language="JavaScript" type="text/javascript">
    (function(){
    function CountdownTimer(elm,tl,mes){
     this.initialize.apply(this,arguments);
    }
    CountdownTimer.prototype={
     initialize:function(elm,tl,mes) {
      this.elem = document.getElementById(elm);
      this.tl = tl;
      this.mes = mes;
     },countDown:function(){
      var timer='';
      var today=new Date();
      var day=Math.floor((this.tl-today)/(24*60*60*1000));
      var hou=Math.floor(((this.tl-today)%(24*60*60*1000))/(60*60*1000));
      var min=Math.floor(((this.tl-today)%(24*60*60*1000))/(60*1000))%60;
      var sec=Math.floor(((this.tl-today)%(24*60*60*1000))/1000)%60%60;
      var mil=Math.floor(((this.tl-today)%(24*60*60*1000))/10)%100;
      var me=this;
    
      if( ( this.tl - today ) > 0 ){
       if (day) timer += '<span class="day">'+day+'</span><span class="moji">日</span>';
       if (hou) timer += '<span class="hou">'+hou+'</span><span class="moji">時間</span>';
       timer += '<span class="min">'+this.addZero(min)+'</span><span class="moji">分</span><span class="sec">'+this.addZero(sec)+'</span><span class="moji">秒</span><span class="mil">'+this.addZero(mil)+'</span>';
       this.elem.innerHTML = timer;
       tid = setTimeout( function(){me.countDown();},10 );
      }else{
       this.elem.innerHTML = this.mes;
       return;
      }
     },addZero:function(num){ return ('0'+num).slice(-2); }
    }
    function countdown(){
     var tl = new Date('2019/12/14 20:00:00');
     //この上の部分で終了時間を設定するYO！
     var timer = new CountdownTimer('countdown',tl,'販売開始しました');
     //この上の文は終了した後に表示する文字!
     timer.countDown();
    }
    window.addEventListener('load', function(){
     countdown();
    });
    })();
    </script>
    
    <script language="JavaScript" type="text/javascript">
    (function(){
    function CountdownTimer(elm,tl,mes){
     this.initialize.apply(this,arguments);
    }
    CountdownTimer.prototype={
     initialize:function(elm,tl,mes) {
      this.elem = document.getElementById(elm);
      this.tl = tl;
      this.mes = mes;
     },countDown2:function(){
      var timer='';
      var today=new Date();
      var day=Math.floor((this.tl-today)/(24*60*60*1000));
      var hou=Math.floor(((this.tl-today)%(24*60*60*1000))/(60*60*1000));
      var min=Math.floor(((this.tl-today)%(24*60*60*1000))/(60*1000))%60;
      var sec=Math.floor(((this.tl-today)%(24*60*60*1000))/1000)%60%60;
      var mil=Math.floor(((this.tl-today)%(24*60*60*1000))/10)%100;
      var me=this;
    
      if( ( this.tl - today ) > 0 ){
       if (day) timer += '<span class="day">'+day+'</span><span class="moji">日</span>';
       if (hou) timer += '<span class="hou">'+hou+'</span><span class="moji">時間</span>';
       timer += '<span class="min">'+this.addZero(min)+'</span><span class="moji">分</span><span class="sec">'+this.addZero(sec)+'</span><span class="moji">秒</span><span class="mil">'+this.addZero(mil)+'</span>';
       this.elem.innerHTML = timer;
       tid = setTimeout( function(){me.countDown2();},10 );
      }else{
       this.elem.innerHTML = this.mes;
       return;
      }
     },addZero:function(num){ return ('0'+num).slice(-2); }
    }
    function countdown2(){
     var tl = new Date('2019/12/24 20:00:00');
     //この上の部分で終了時間を設定するYO！
     var timer = new CountdownTimer('countdown2',tl,'販売開始しました');
     //この上の文は終了した後に表示する文字!
     timer.countDown2();
    }
    window.addEventListener('load', function(){
     countdown2();
    });
    })();
    </script>
    
    
    <script type="text/javascript">
    var startday =[];
    var endday = [];
    
    startday[1] = new Date('2019/11/1 20:00');
    endday[1] = new Date('2019/12/14 19:59');
    startday[2] = new Date('2019/11/4 20:00');
    endday[2] = new Date('2019/12/24 23:59');
    var today = new Date();
    
    for(i=1;i<=11;i++){
    if(startday[i] > today || today > endday[i] ){
    $('#time'+i).remove();
    }
    }
    </script>
    </head>
    
    <body>
    
    <div id="time1" class="times">
    <div class="box1">
    <div id="inner">
    <div id="text" style="float: left;">販売開始まで</div><div id="countdown" style="float: left;"></div>
    <div style="clear: both;"></div>
    </div>
    </div>
    </div>
    
    <div id="time2" class="times">
    <div class="box1">
    <div id="inner2">
    <div id="text2" style="float: left;">販売終了まで</div><div id="countdown2" style="float: left;"></div>
    <div style="clear: both;"></div>
    </div>
    </div>
    </div>
    
    
    </body>
    </html>
[/code]


CC BY-SA 4.0
2019-11-30T09:25:43.820
60964
2019-11-30T09:31:35.597
2019-11-30T09:31:35.597
7347
7347
60961
answer