作为fizzbuzz概念的变体,以及作为学习JavaScript,HTML5和CSS的练习(我对它们都不十分了解)。

常规的fizzbuzz有点累,但是拥有一个Web-基于可视化的输出似乎很有用,并且能够随意调整输入,使您可以看到模数变化的影响。




 var fizzLoaded = false;
var fizzDiv, fizzFrom, fizzTo, fizzPlayers;

function fizzLoad() {
  if (fizzLoaded) {
    return;
  }
  fizzLoaded = true;
  var fizzForm = document.getElementById('fizzbuzz');
  fizzFrom = document.getElementById('rangeFrom');
  fizzTo = document.getElementById('rangeTo');
  fizzPlayers = [
    document.getElementById('frodo'),
    document.getElementById('sam'),
    document.getElementById('merry'),
    document.getElementById('pippin'),
    document.getElementById('bilbo')
  ];
  fizzDiv = document.getElementById('fizzOut');
}

function restrictRange() {
  var rFrom = parseInt(fizzFrom.value);
  var rTo = parseInt(fizzTo.value);
  fizzTo.min = rFrom;
  fizzFrom.max = rTo;
}

function validateValues() {
  var rFrom = parseInt(fizzFrom.value);
  var rTo = parseInt(fizzTo.value);
  if (rTo < rFrom) {
    alert("Illegal range from " + rFrom + " to " + rTo);
    return false;
  }
  for (var i = 0; i < fizzPlayers.length; i++) {
    var val = parseInt(fizzPlayers[i].value);
    if (val < 0 || val > 100) {
      alert("Illegal value " + val + " for player " + fizzPlayers[i].id);
      return false;
    }
  }
  return true;
}

function capitaliseFirstLetter(string) {
      return string.charAt(0).toUpperCase() + string.slice(1);
}

function fizzing() {
  fizzLoad();
  restrictRange();
  if (!validateValues()) {
    fizzDiv.innerHTML = "Illegal inputs";
    return;
  }

  var table = "";
  var rFrom = parseInt(fizzFrom.value);
  var rTo = parseInt(fizzTo.value);
  var active = [];
  var actfact = [];
  var actname = [];
  var player;

  for (var i = 0; i < fizzPlayers.length; i++) {
    player = fizzPlayers[i];
    var val = parseInt(player.value);
    if (val != 0) {
      active.push(player);
      actfact.push(parseInt(player.value));
      actname.push(capitaliseFirstLetter(player.id));
    }
  }

  table += "<table>\n";
  table += "  <tr><th>Value</th><th>Message</th></tr>\n";
  for (var i = rFrom; i <= rTo; i++) {

    var msg = "";
    for (var p = 0; p < active.length; p++) {
      if (i % actfact[p] == 0) {
        msg += actname[p];
      }
    }
    if (msg == "") {
      msg = "" + i;
    }
    table += "  <tr><td>" + i + "</td><td>" + msg + "</td></tr>\n";
  }
  table += "</table>\n";

  fizzDiv.innerHTML = table;
} 

 h1 {
    clear: left;
}

hr {
    clear: left;
}

label {
    display: inline-block;
    float: left;
    clear: left;
    width: 150px;
    text-align: left;
}
input {
  display: inline-block;
  float: right;
  text-align: right;
  padding-left:10px;
  width: 50px;
} 

 <!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>FizzBuzz</title>
    <link rel="stylesheet" href="fizzbuzz.css">
    <script src="fizzbuzz.js" type="text/javascript"></script>
  </head>
  <body>
    <h1>Config</h1>
    <form id="fizzbuzz">
      <fieldset id="fizzControl" oninput="fizzing();">
        <label>Range From<input id="rangeFrom" type="number" min="1" max="100" value="1" required></label>
        <label>Range To<input id="rangeTo"   type="number" min="1" max="1024" value="100" required></label>
        <div id="players" >
          <label>Frodo<input id="frodo" type="number" min="0" max="100" value="3" required></label>
          <label>Sam<input id="sam" type="number" min="0" max="100" value="5" required></label>
          <label>Merry<input id="merry" type="number" min="0" max="100" value="0" required></label>
          <label>Pippin<input id="pippin" type="number" min="0" max="100" value="0" required></label>
          <label>Bilbo<input id="bilbo" type="number" min="0" max="100" value="0" required></label>
        </div>
      </fieldset>
    </form>
    <hr>
    <h1>Output</h1>
    <div id="fizzOut" >Change a value to get output (a snippet thing)
    <script>fizzing();</script>
  </body>
</html> 





我这里有许多让我感到沮丧的问题,我相信有更好的方法可以解决这些问题...:

CSS:<我非常努力地将格式/样式与HTML隔离开来,但是....


输入控件的对齐要求left: clear才能获取每个在其行上
我必须指定标签的宽度
类似地,h1hr都需要left:clear


HTML:

我担心我必须在最后添加脚本部分来“初始化”输出。请注意,这在我的服务器上运行良好,但是在堆栈片段上,您必须更改输入值才能更新输出。 ..

JavaScript:

这是我想说的很多话,我相信这里有不良做法。特别让我感到沮丧的是:


我无法使for (player in fizzPlayers) ...正常工作,而播放器始终是undefined。我被迫做诸如for (var i = 0; var ...)之类的事情,然后再引用回fizzPlayers数组。我在这里错过了一些东西,我知道,必须有一种方法可以只声明一次文档查找,而不必求助于全局变量。

除了我一直在奋斗的那些领域,而且我肯定必须有更好的方法,还有其他我犯错的地方吗? />

评论

有趣的事实,这在我之前从未发生过。使用值2,3,5,7,11会产生一种粗质数筛。

#1 楼

正在加载

这是非常规的:


var fizzLoaded;

function fizzLoad() {
  if (fizzLoaded) {
    return;
  }
  fizzLoaded = true;
  …
}



卸下“锁”没有任何危害。但是,更优雅的方法是编写

window.onload = function fizzLoad() {
    …
};


您可以使用onload处理程序以默认设置立即填充输出。


activeactfactactname


我不建议维护这样的三个并行数组:

>

使用一组对象会更容易理解:

var val = parseInt(player.value);
if (val != 0) {
  active.push(player);
  actfact.push(parseInt(player.value));
  actname.push(capitaliseFirstLetter(player.id));
}


处理五个玩家
比按ID提取五个元素要好:


var val = parseInt(player.value);
if (val != 0) {
  active.push({ name: capitaliseFirstLetter(player.id),
                factor: val });
}



最好将它们统一对待,这样就不必对这五个ID进行硬编码:一个问题是,将来使用grep输入名称代码的维护人员会惊讶地发现名称在代码中为小写,但在输出中为大写。另一个问题是标识符应该是标识符-它们必须遵守规则。

事件处理和输入验证

我不建议使用oninput,因为它触发起来太容易了。用户可能试图修复错误,并在过程中再次被警报打断。由于警报是模式对话框,因此这尤其令人沮丧。每个输入元素上都有一个onchange处理程序会更合适。它周围的所有东西都需要避免不适当地回流。尽量少用它们。

您当然不需要浮动<label>元素。标签,如下文所述。使用<table>也将是合理的。




 var fizzFrom, fizzTo, fizzPlayers, fizzDiv;

window.onload = function fizzLoad(event) {
  // Associate inputs with their labels (https://stackoverflow.com/a/285608)
  var labels = document.getElementsByTagName('label');
  for (var i = 0; i < labels.length; i++) {
    if (labels[i].htmlFor != '') {
      var elem = document.getElementById(labels[i].htmlFor);
      if (elem) elem.dataset.labelHTML = labels[i].innerHTML;
    }
  }
  
  fizzFrom = document.getElementById('rangeFrom');
  fizzTo   = document.getElementById('rangeTo');
  fizzFrom.onchange = fizzTo.onchange = fizzChanged;
  
  fizzPlayers = document.getElementById('players').getElementsByTagName('input');
  for (var e = 0; e < fizzPlayers.length; e++) {
    fizzPlayers[e].onchange = fizzChanged;
  }
  
  fizzDiv = document.getElementById('fizzOut');
  
  fizzChanged();
};

function validate() {
  // Omitted for brevity
  return true;
}

function fizzChanged(event) {
  if (!validate()) return;

  var table = "";
  var rFrom = parseInt(fizzFrom.value);
  var rTo = parseInt(fizzTo.value);
  var active = [];

  for (var i = 0; i < fizzPlayers.length; i++) {
    var player = fizzPlayers[i];
    var val = parseInt(player.value);
    if (val != 0) {
      active.push({ name: player.dataset.labelHTML, factor: val });
    }
  }

  table += "<table>\n";
  table += "  <tr><th>Value</th><th>Message</th></tr>\n";
  for (var i = rFrom; i <= rTo; i++) {

    var msg = "";
    for (var p = 0; p < active.length; p++) {
      if (i % active[p].factor == 0) {
        msg += active[p].name;
      }
    }
    if (msg == "") {
      msg = i;
    }
    table += "  <tr><td>" + i + "</td><td>" + msg + "</td></tr>\n";
  }
  table += "</table>\n";

  fizzDiv.innerHTML = table;
} 

 label {
    display: inline-block;
    width: 100px;
}
input {
    padding-left: 10px;
    width: 50px;
} 

 <fieldset>
  <div><label for="rangeFrom">Range From</label>
       <input id="rangeFrom" type="number" min="1" max="100" value="1" required>
  </div>
  <div><label for="rangeTo">Range To</label>
        <input id="rangeTo"  type="number" min="1" max="1024" value="100" required>
  </div>
  <div id="players">
    <div><label for="frodo">Frodo</label>
         <input id="frodo" type="number" min="0" max="100" value="3" required>
    </div>
    <div><label for="sam">Sam</label>
         <input id="sam" type="number" min="0" max="100" value="5" required>
    </div>
    <div><label for="merry">Merry</label>
         <input id="merry" type="number" min="0" max="100" value="0" required>
    </div>
    <div><label for="pippin">Pippin</label>
         <input id="pippin" type="number" min="0" max="100" value="0" required>
    </div>
    <div><label for="bilbo">Bilbo</label>
         <input id="bilbo" type="number" min="0" max="100" value="0" required>
    </div>
  </div>
</fieldset>
<hr>
<h1>Output</h1>
<div id="fizzOut"></div> 




评论


\ $ \ begingroup \ $
DOMContentLoaded不是正确的事件吗?
\ $ \ endgroup \ $
– CodesInChaos
15年7月4日在18:54

\ $ \ begingroup \ $
@CodesInChaos是的,那将是“现代”的方式。
\ $ \ endgroup \ $
– 200_success
15年7月4日在18:55

#2 楼

您的CSS看起来非常好,尽管您是否确实都需要hlhr两者是否都做同样的事情?您为什么不只摆脱其中之一?

您的HTML中有一个错误-未关闭的div(http://validator.w3.org/check):

  <div id="fizzOut" >Change a value to get output (a snippet thing)
  <script>fizzing();</script>
</body>


应关闭div

<div id="fizzOut">Change a value to get output (a snippet thing)</div>


#3 楼

名称

我发现在命名中虽然看起来一致,但不断使用fizz前缀有点奇怪,因为运行时会使用Fizz或Buzz以外的其他词。

var fizzDiv, fizzFrom, fizzTo, fizzPlayers;


大写首字母

这似乎使您正在做的工作量加倍:

  fizzLoaded = true;
  var fizzForm = document.getElementById('fizzbuzz');
  fizzFrom = document.getElementById('rangeFrom');
  fizzTo = document.getElementById('rangeTo');
  fizzPlayers = [
    document.getElementById('frodo'),
    document.getElementById('sam'),
    document.getElementById('merry'),
    document.getElementById('pippin'),
    document.getElementById('bilbo')
  ];
  fizzDiv = document.getElementById('fizzOut');
}


这:

function capitaliseFirstLetter(string) {
      return string.charAt(0).toUpperCase() + string.slice(1);
}


也许用大写字母声明它们会更理想?我敢肯定您这样做是有原因的,但对我来说似乎并不是最优雅。

还有一件事情

如果使用大于100的数字并且开始看到具有3-4个匹配项的值,那么在它们之间留一个空格将是很不错的选择。例如:


330 FrodoSamPippin


阅读效果更好:


330 Frodo Sam Pippin


评论


\ $ \ begingroup \ $
按照惯例,HTML中的属性使用camelCase,而元素使用PascalCase。
\ $ \ endgroup \ $
–RubberDuck
2014年12月22日在20:02

#4 楼

在数组元素上编写循环的惯用现代JavaScript方法是forEach。 br />将for...in与数组一起使用时,不是变量要填充到循环变量中,而是它们的索引。 br />
fizzPlayers.forEach(function(player) {
  var val = parseInt(player.value);
  if (val != 0) {
    active.push(player);
    actfact.push(parseInt(player.value));
    actname.push(capitaliseFirstLetter(player.id));
  }
});


for...in重写会比较棘手,
因为您需要从循环返回。索引而不是元素:

  for (var i in fizzPlayers) {
    var player = fizzPlayers[i];
    var val = parseInt(player.value);
    if (val < 0 || val > 100) {
      alert("Illegal value " + val + " for player " + player.id);
      return false;
    }
  }


有关更多信息,请参阅文档以及有关SO的相关文章避免污染全局名称空间的常见方法是将变量放入通常称为fizzing的单个全局对象中,例如:

var App = window.App = {};


然后在全局变量中引用变量命名空间,
您应该在validateValues中引用它们: br />
变量名通常不是很好:关于for..inApp,为什么不简单地使用AppfizzForm




 fizzFrom 

 rTo 

 rFrom 




#5 楼

根据JSLint,您应该使用四空间缩进,而不是两空间缩进(http://jslint.com/)。我不知道这方面的官方标准。

var fizzLoaded = false;
var fizzDiv, fizzFrom, fizzTo, fizzPlayers;

function fizzLoad() {
    if (fizzLoaded) {
        return;
    }
    fizzLoaded = true;
    var fizzForm = document.getElementById('fizzbuzz');
    fizzFrom = document.getElementById('rangeFrom');
    fizzTo = document.getElementById('rangeTo');
    fizzPlayers = [
        document.getElementById('frodo'),
        document.getElementById('sam'),
        document.getElementById('merry'),
        document.getElementById('pippin'),
        document.getElementById('bilbo')
    ];
    fizzDiv = document.getElementById('fizzOut');
}

function restrictRange() {
    var rFrom = parseInt(fizzFrom.value);
    var rTo = parseInt(fizzTo.value);
    fizzTo.min = rFrom;
    fizzFrom.max = rTo;
}

function validateValues() {
    var rFrom = parseInt(fizzFrom.value);
    var rTo = parseInt(fizzTo.value);
    if (rTo < rFrom) {
        alert("Illegal range from " + rFrom + " to " + rTo);
        return false;
    }
    for (var i = 0; i < fizzPlayers.length; i++) {
        var val = parseInt(fizzPlayers[i].value);
        if (val < 0 || val > 100) {
            alert("Illegal value " + val + " for player " + fizzPlayers[i].id);
            return false;
        }
    }
    return true;
}

function capitaliseFirstLetter(string) {
            return string.charAt(0).toUpperCase() + string.slice(1);
}

function fizzing() {
    fizzLoad();
    restrictRange();
    if (!validateValues()) {
        fizzDiv.innerHTML = "Illegal inputs";
        return;
    }

    var table = "";
    var rFrom = parseInt(fizzFrom.value);
    var rTo = parseInt(fizzTo.value);
    var active = [];
    var actfact = [];
    var actname = [];
    var player;

    for (var i = 0; i < fizzPlayers.length; i++) {
        player = fizzPlayers[i];
        var val = parseInt(player.value);
        if (val != 0) {
            active.push(player);
            actfact.push(parseInt(player.value));
            actname.push(capitaliseFirstLetter(player.id));
        }
    }

    table += "<table>\n";
    table += "    <tr><th>Value</th><th>Message</th></tr>\n";
    for (var i = rFrom; i <= rTo; i++) {

        var msg = "";
        for (var p = 0; p < active.length; p++) {
            if (i % actfact[p] == 0) {
                msg += actname[p];
            }
        }
        if (msg == "") {
            msg = "" + i;
        }
        table += "    <tr><td>" + i + "</td><td>" + msg + "</td></tr>\n";
    }
    table += "</table>\n";

    fizzDiv.innerHTML = table;
}


评论


\ $ \ begingroup \ $
Stack Snippet编辑器执行两个空格。也许您应该在Meta上提交错误报告。
\ $ \ endgroup \ $
– 200_success
2014年12月23日下午6:43

#6 楼

内联块,浮动和清除?!

浮动元素(内联块,表格单元格等)时,大多数显示属性都会被忽略。要么浮动它,要么将其更改为内联块,而不是两者都。但是,您真正要在这里找到的是:

label {
    display: table;
}


现在您可以删除float和clear属性。 br />
左是默认对齐方式,您无需在标签上指定它(我看不到任何地方都可以覆盖先前的对齐声明)。

文本元素的px宽度

一般来说,使用px来指定文本元素的宽度并不安全,因为用户是最终使用所使用字体大小的人。理想情况下,您应该使用ems。标签和输入元素都适用。

字段集

您正在使用字段集,但从未使用图例?似乎是一个奇怪的选择。 fieldset元素旨在将相似的控件组合在一起,而不仅仅是包含所有内容。如果是我,那将是2个字段集:

<fieldset id="fizzControl">
    <legend>Iterations</legend>

    <label>Range From<input id="rangeFrom" type="number" min="1" max="100" value="1" required></label>
    <label>Range To<input id="rangeTo"   type="number" min="1" max="1024" value="100" required></label>
</fieldset>

<fieldset id="players" >
    <legend>Players</legend>

    <label>Frodo<input id="frodo" type="number" min="0" max="100" value="3" required></label>
    <label>Sam<input id="sam" type="number" min="0" max="100" value="5" required></label>
    <label>Merry<input id="merry" type="number" min="0" max="100" value="0" required></label>
    <label>Pippin<input id="pippin" type="number" min="0" max="100" value="0" required></label>
    <label>Bilbo<input id="bilbo" type="number" min="0" max="100" value="0" required></label>
</fieldset>


#7 楼


for (var i = 0; i < fizzPlayers.length; i++) {
    player = fizzPlayers[i];
    var val = parseInt(player.value);
    if (val != 0) {
        active.push(player);
        actfact.push(parseInt(player.value));
        actname.push(capitaliseFirstLetter(player.id));
    }
}



我会稍作修改

首先,我将在调用val方法时使用actfact.push()变量,两次调用parseInt

for (var i = 0; i < fizzPlayers.length; i++) {
  player = fizzPlayers[i];
  var val = parseInt(player.value);
  if (val != 0) {
    active.push(player);
    actfact.push(val);
    actname.push(capitaliseFirstLetter(player.id));
  }
}


,但是我建议像这样完全删除该变量:
>这将继续进行下一次迭代,仅创建一个变量并沿线性方向移动。