【问题标题】:Javascript not able to make common functions for a prototypeJavascript 无法为原型制作通用功能
【发布时间】:2017-02-28 18:43:05
【问题描述】:

我正在尝试使用原型在 javascript 中制作计时器。每次创建一个新的计时器时,都会创建一个原型对象。有增加时间和每秒打印的方法。整个代码sn-p如下:

function Timer(elem) {

  this.interval = null;
  this.currentTime = {
    sec: 0,
    min: 0,
    hr: 0
  };
  this.elem = elem;
};

Timer.prototype.start = function() {
  var self = this;
  if (!self.interval) {
    self.interval = setInterval(update, 1000);
  }

  function update() {
    incrementTime();
    render();
  }

  function render() {
    self.elem.innerText = getPrintableTime();
  }

  function incrementTime() {
    self.currentTime["min"] += Math.floor((++self.currentTime["sec"]) / 60);
    self.currentTime["hr"] += Math.floor(self.currentTime["min"] / 60);
    self.currentTime["sec"] = self.currentTime["sec"] % 60;
    self.currentTime["min"] = self.currentTime["min"] % 60;
  }

  function getPrintableTime() {
    var text = getTwoDigitNumber(self.currentTime["hr"]) + ":" + getTwoDigitNumber(self.currentTime["min"]) + ":" + getTwoDigitNumber(self.currentTime["sec"]);
    return text;
  }

  function getTwoDigitNumber(number) {
    if (number > 9) {
      return "" + number;
    } else {
      return "0" + number;
    }
  }
};
module.exports = Timer;

我在start 函数中有所有方法。问题是对于Timer 的每个新对象,每个方法都会使用新空间,这是非常低效的。但是当我尝试将方法放在start 函数之外时,它们将无法访问self 变量。您可以看到使用了setInterval 函数,它将每秒调用这些方法。我也不能使用this,因为this 将在后续调用中成为Window 的实例。

我怎样才能通过只保留所有内部方法的一个实例来解决这种情况?

【问题讨论】:

  • 如何创建Timer 的新对象以及如何调用。你能举个例子吗。问题似乎不在您的代码中,而是出现在您的调用方法中。
  • 那么为什么不使用完全原型模式呢?
  • @epascarello 不想暴露其他功能。
  • 您正在使用所选解决方案公开它们!!!!大声笑
  • 我在两者之间更改了选定的解决方案。但是,如果您是在谈论以后的选择,那么请解释一下。

标签: javascript oop prototype


【解决方案1】:

您不需要在start 函数中有所有 方法。是的,对于每个新的 Timer 实例,将使用每个函数的新空间,但是当您想要使用 setInterval 时这是必要的,因为您需要一个关闭实例的函数。但是,您只需要一个这样的闭包,其他方法可以是标准原型方法。

function getTwoDigitNumber(number) {
    return (number > 9 ? "" : "0") + number;
}

function Timer(elem) {
    this.interval = null;
    this.currentTime = {
        sec: 0,
        min: 0,
        hr: 0
    };
    this.elem = elem;
};

Timer.prototype.start = function() {
    var self = this;
    if (!this.interval) {
        this.interval = setInterval(function update() {
            self.incrementTime();
            self.render();
        }, 1000);
    }
};
Timer.prototype.render() {
    this.elem.innerText = this.getPrintableTime();
};
Timer.prototype.incrementTime = function() {
    this.currentTime.sec += 1;
    this.currentTime.min += Math.floor(this.currentTime.sec / 60);
    this.currentTime.hr += Math.floor(this.currentTime.min / 60);
    this.currentTime.sec = this.currentTime.sec % 60;
    this.currentTime.min = this.currentTime.min % 60;
};
Timer.prototype.getPrintableTime = function() {
    var text = getTwoDigitNumber(this.currentTime.hr) + ":"
             + getTwoDigitNumber(this.currentTime.min) + ":"
             + getTwoDigitNumber(self.currentTime.sec);
    return text;
};

module.exports = Timer;

顺便说一句,关于你的incrementTime 模式,你应该看看How to create an accurate timer in javascript?

【讨论】:

  • 如果只有 start 方法需要是“公共的”,那么在原型中暴露其他方法就是破坏封装
  • @Stubb0rn 和做 OP 想做的事情是一样的,打破封装....
  • @Stubb0rn 如果您想要“私有”方法,您只能使用将实例作为参数传递的静态函数。或者使用下划线命名约定。所有方法在 JS 中都是公开的,但无论如何这不是问题 - 这是关于将通用函数放在原型上。
  • @Bergi 有什么办法可以避免制作Timer的每个函数原型。虽然我不想破坏封装,但只是为了避免每种方法的多个实例。如何实现其他选项,使用静态函数获取实例作为参数?
  • @ShashwatKumar 就像我对getTwoDigitNumber 所做的那样 - 而不是通过number,而是通过self
【解决方案2】:

您可以使用apply 来使用在原型之外定义的函数,并带有正确的this 上下文。

function Timer(elem) {

  this.interval = null;
  this.currentTime = {
    sec: 0,
    min: 0,
    hr: 0
  };
  this.elem = elem;
};

function update() {
  incrementTime.apply(this);
  render.apply(this);
}

function render() {
  this.elem.innerText = getPrintableTime.apply(this);
}

function incrementTime() {
  this.currentTime["min"] += Math.floor((++this.currentTime["sec"]) / 60);
  this.currentTime["hr"] += Math.floor(this.currentTime["min"] / 60);
  this.currentTime["sec"] = this.currentTime["sec"] % 60;
  this.currentTime["min"] = this.currentTime["min"] % 60;
}

function getPrintableTime() {
  var text = getTwoDigitNumber(this.currentTime["hr"]) + ":" + getTwoDigitNumber(this.currentTime["min"]) + ":" + getTwoDigitNumber(this.currentTime["sec"]);
  return text;
}

function getTwoDigitNumber(number) {
  if (number > 9) {
    return "" + number;
  } else {
    return "0" + number;
  }
}

Timer.prototype.start = function() {
  var self = this;
  if (!self.interval) {
    self.interval = setInterval(function() {
      update.apply(self);
    }, 1000);
  }
};

document.addEventListener('DOMContentLoaded', function() {
  var timer = new Timer(document.getElementById('timer'));
  timer.start();
}, false);
<div id="timer"></div>

【讨论】:

  • 我试过了,但它不起作用。在后续调用函数期间,this 指向 Window 而不是 Timer
  • this 将指向 Window 仅当原型之外的函数被调用而不使用 apply
  • 这行得通。我之前用错了。这正是我想要的。
【解决方案3】:

如果我理解正确,您只想创建一个间隔。

一种可能的解决方案是创建一个静态方法和变量来管理setInterval。我会注意到,虽然这可能对性能更友好,但计时器将始终以相同的计数启动和运行……而不是从创建每个计时器的那一刻起。 (见示例)

当然,您可以捕获当前时间戳并从那里计算经过的时间。但是,那是另一个线程;)

function Timer(elem) {
  this.interval = null;
  this.currentTime = {
    sec: 0,
    min: 0,
    hr: 0
  };
  this.elem = elem;
};

Timer.subscribe = function(timer) {
  Timer.subscribers = Timer.subscribers || [];
  if (Timer.subscribers.indexOf(timer) === -1) {
    Timer.subscribers.push(timer);
    timer.update.call(timer);
  }
  Timer.checkInterval();
};

Timer.unsubscribe = function(timer) {
  Timer.subscribers = Timer.subscribers || [];
  if (Timer.subscribers.indexOf(timer) !== -1) {
    Timer.subscribers.splice(Timer.subscribers.indexOf(timer), 1);
  }
  Timer.checkInterval();
};

Timer.checkInterval = function() {
  if (!Timer.interval && Timer.subscribers.length > 0) {
    Timer.interval = setInterval(function() {
      Timer.subscribers.forEach(function(item) {
        item.update.call(item);
      });
    }, 1000);
  } else if (Timer.interval && Timer.subscribers.length === 0) {
    clearInterval(Timer.interval);
    Timer.interval = null;
  }
};

Timer.prototype = {
  start: function() {
    Timer.subscribe(this);
  },
  
  stop: function() {
    Timer.unsubscribe(this);
  },
  
  update: function() {
    this.incrementTime();
    this.render();
  },
  
  incrementTime: function() {
    this.currentTime["min"] += Math.floor((++this.currentTime["sec"]) / 60);
    this.currentTime["hr"] += Math.floor(this.currentTime["min"] / 60);
    this.currentTime["sec"] = this.currentTime["sec"] % 60;
    this.currentTime["min"] = this.currentTime["min"] % 60;
  },
  
  render: function() {
    var self = this;

    function getPrintableTime() {
      var text = getTwoDigitNumber(self.currentTime["hr"]) + ":" + getTwoDigitNumber(self.currentTime["min"]) + ":" + getTwoDigitNumber(self.currentTime["sec"]);
      return text;
    }

    function getTwoDigitNumber(number) {
      if (number > 9) {
        return "" + number;
      } else {
        return "0" + number;
      }
    }
    
    this.elem.innerText = getPrintableTime();
  }
};

/**
 *
 */
var timers = document.getElementById('timers');

function addTimer() {
  var el = document.createElement('div');
  var tmr = document.createElement('span');
  var btn = document.createElement('button');
  var t = new Timer(tmr);
  
  btn.innerText = 'Stop';
  btn.onclick = function() {
    t.stop();
  };
  
  el.appendChild(tmr);
  el.appendChild(btn);
  timers.appendChild(el);
  
  t.start();
};
<div id="timers"></div>

<button onclick="addTimer()">Add Timer</button>

【讨论】:

    猜你喜欢
    • 2015-04-19
    • 1970-01-01
    • 1970-01-01
    • 2019-03-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多