从选择和重新附加更改中删除选项

Don*_*own 8 html javascript jquery

我刚刚在选择元素中删除和重新附加选项时意识到了一种奇怪的行为.如果选择了其中一个选项,则在附加后,将选择下一个项目而不是原始项目.考虑以下html:

var $opts = $("#sel option").remove();
console.log($opts);
$("#sel").append($opts);
Run Code Online (Sandbox Code Playgroud)
<select id="sel">
    <option>A</option>
    <option>B</option>
    <option selected="selected">C</option>
    <option>D</option>
</select>
(Look in the console.)
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
Run Code Online (Sandbox Code Playgroud)

(或作为一个小提琴)

它使得选项具有值"D",而不是具有值的选项"C",如最初定义的那样.注意控制台中打印的选项,selectedremove()方法后属性会发生变化.

为什么会这样?

注意:我知道如何修复它或解决它,这不是问题.问题是为什么会发生?

T.J*_*der 5

有趣的问题。

我认为,这取决于浏览器在删除唯一选择的选项时自动选择一个选项,然后将选项添加到selected设置了标志的框中会发生什么。它也似乎是特定于浏览器的,这也许不足为奇。但更多的是,Chrome会根据我们的观看程度来改变其行为。这确实让我感到惊讶。详细信息如下。

首先,让我们使用诸如更新后的示例之类的内容,但首先选择的内容既不是第一个也不是最后一个,而是看到了最终的结果。因此,我们将得到A,B,C,D和E,然后从选择的B开始:

$("input[type=button]").on("click", function() {
  var $opts = $("#sel option").remove();
  $("#sel").append($opts);
});
Run Code Online (Sandbox Code Playgroud)
<select id="sel">
    <option>A</option>
    <option selected="selected">B</option>
    <option>C</option>
    <option>D</option>
    <option>E</option>
</select>
<input type="button" value="Go">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
Run Code Online (Sandbox Code Playgroud)

我们观察到的结果因浏览器而异:

  • 铬对C,一个结束了之后这是一开始(不是最后一个)选择的一个。
  • IE11以B结尾,也就是说,在我们删除/添加时,似乎没有更改所选标志。
  • Firefox以E(最后一个)结尾。

更糟的是,Chrome浏览器的行为会根据我们是否观察到元素的属性而有所不同selected。Heisenbug!如果我们观察到它,它最终将执行Firefox(E)而不是C的操作。

这为我们提供了有关Chrome中发生的情况的重要线索。但是,让我们从简单的开始:

IE11

似乎IE11只是留下了这些标志,可能是因为它不担心在JavaScript代码运行时仅在它退回并且IE更新其UI时更新它们的担心。

火狐浏览器

如果selected在框中未选择任何选项时主动设置选项上的标志,则Firefox有意义(因为我们删除了选中的选项):

  1. 我们删除A,B保持选中状态。
  2. 我们删除了B,因为B不在框中,所以C被选中,并且该框需要至少有一个选中的选项。删除的选项的标志保持设置状态,这一点在一分钟内很重要
  3. 当我们删除C和D时,重复步骤2。
  4. 我们删除E。至此,C,D和E都设置了标志。
  5. 我们在框中添加A。由于它是盒子唯一的选择,因此它会selected设置其标志。
  6. 我们在框中添加B。由于selected设置了B的标志,因此它成为选定的属性,而A被取消选择。
  7. 当我们重新添加C,D和E时,重复步骤6。
  8. 我们最终选择了E。

我们可以观察IE11和Firefox的发生,因为它们不会根据我们是否观察到它们而改变其行为:

var $opts = $("#sel option");
showOptions("Before", $opts);
$opts.each(function() {
  var opt = $(this);
  opt.remove();
  showOptions("After removing " + opt.text(), $opts);
});
$opts.each(function() {
  var opt = $(this);
  $("#sel").append(opt);
  showOptions("After appending " + opt.text(), $opts);
});
function showOptions(label, $opts) {
  var row = $("<tr>");
  row.append($("<td>").text(label).addClass("label"));
  $opts.each(function() {
    row.append($("<td>").text(this.selected ? "*" : "o"));
  });
  row.appendTo("#results");
}
Run Code Online (Sandbox Code Playgroud)
th, td {
  min-width: 3em;
  text-align: center;
  padding: 4px;
}
table, th, td {
  border-collapse: collapse;
  border: 1px solid #eee;
}
.label {
  text-align: left;
}
Run Code Online (Sandbox Code Playgroud)
<select id="sel">
    <option>A</option>
    <option selected="selected">B</option>
    <option>C</option>
    <option>D</option>
    <option>E</option>
</select>
<table>
  <thead>
    <tr>
      <th></th>
      <th>A</th>
      <th>B</th>
      <th>C</th>
      <th>D</th>
      <th>E</th>
    </tr>
  </thead>
  <tbody id="results">
  </tbody>
</table>
* = selected, o = not selected

<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
Run Code Online (Sandbox Code Playgroud)

这使我们陷入困境:Chrome。Chrome会根据我们是否selected随便观察该属性来更改其行为。具体来说,如果我们观察selected到删除它们时仍在选择框中的选项,它将改变其行为。它不关心,如果我们看一下selected在去除的,它不会不管我们看一下selected,我们要追加。就像我们要删除的一样,还有我们还没有删除的那些。

首先,这是证明:

$("#btn").on("click", function() {
  var observeLTE = true;
  var observeGT = true;
  var index;
  var $opts = $("#sel option");
  showOptions("Before", $opts);
  observeLTE = $("#cbremove")[0].checked;
  observeGT = $("#cbremoveun")[0].checked;
  $opts.each(function(i) {
    var opt = $(this);
    index = i;
    opt.remove();
    showOptions("After removing " + opt.text(), $opts);
  });
  observeLTE = observeGT = $("#cbappend")[0].checked;
  $opts.each(function(i) {
    var opt = $(this);
    index = i;
    $("#sel").append(opt);
    showOptions("After appending " + opt.text(), $opts);
  });

  function showOptions(label, $opts) {
    var row = $("<tr>");
    row.append($("<td>").text(label).addClass("label"));
    $opts.each(function(i) {
      var selected;
      if ((observeLTE && i <= index) || (observeGT && i > index)) {
        selected = this.selected ? "*" : "o";
      } else {
        selected = "-";
      }
      row.append($("<td>").text(selected));
    });
    row.appendTo("#results");
  }
});
Run Code Online (Sandbox Code Playgroud)
th, td {
  min-width: 3em;
  text-align: center;
  padding: 4px;
}
table, th, td {
  border-collapse: collapse;
  border: 1px solid #eee;
}
.label {
  text-align: left;
}
Run Code Online (Sandbox Code Playgroud)
<div>
  <label>
    <input id="cbremove" type="checkbox">Observe <code>selected</code> on removed options while removing
  </label>
</div>
<div>
  <label>
    <input id="cbremoveun" type="checkbox">Observe <code>selected</code> on UNremoved options while removing
  </label>
</div>
<div>
  <label>
    <input id="cbappend" type="checkbox">Observe <code>selected</code> while appending
  </label>
</div>
<select id="sel">
  <option>A</option>
  <option selected="selected">B</option>
  <option>C</option>
  <option>D</option>
  <option>E</option>
</select>
<input id="btn" type="button" value="Go">
<table>
  <thead>
    <tr>
      <th></th>
      <th>A</th>
      <th>B</th>
      <th>C</th>
      <th>D</th>
      <th>E</th>
    </tr>
  </thead>
  <tbody id="results">
  </tbody>
</table>
* = selected, o = not selected, - = not observed

<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
Run Code Online (Sandbox Code Playgroud)

如果您旋转一下(显然是在Chrome上),则无论您对第一个或最后一个复选框进行何种操作,都将最终以C结束,但是选中中间的复选框将使您最终以E结束。

这给了我们足够的数据来推断的解释。我不能说这实际上解释,只是它适合可用数据。如果有人想更全面,可以在调试版本中逐步浏览Chrome的内部代码。我认为那太过分了。

因此,这里是我的回答,我再次强调,被推断

  1. Chrome 延迟设置实例的selected属性。也就是说,如果您不读取属性的值,Chrome不会检查以确保该属性反映事物的当前状态。HTMLOptionElement
  2. selected排队change事件时,它读取属性。
  3. change如果该选择框已经挂起,则它不会将事件排队。

基于此,我认为这是在Chrome中发生的情况:

  1. 我们删除A,B保持选中状态。没有change事件,因为值没有更改。
  2. 我们删除了B,因为B不在框中,所以C被选中,并且该框需要至少有一个选中的选项。这使change事件排队,该事件观察selected并因此将属性设置为C。
  3. 我们删除了C。从某种意义上说,D被选中(但在某种意义上),但是由于有一个change待处理的事件,因此没有新change事件排队,因此selected不会观察到D,并且其值也不会更新。它仍然false即使我们读它,它会变成true
  4. 删除D时再次相同。没有更改事件,没有观察。
  5. 我们最终获得具有选择的元素selected作为true对B和C,但没有其他人,因为d和E,而他们所选择的选项,并没有更新他们的价值并没有观察到。我们知道这些是它们的价值,因为那时我们可以观察它们。
  6. 我们按顺序添加元素,与Firefox一样,添加一个元素,该selected = true元素在已经选择了另一个元素时取消选择上一个元素。因此,在添加C时取消选择B。当我们添加D和E时,由于C的selected属性为,所以C会被保留false

,找出来很有趣。