javascript - 从选择中删除选项并重新附加选定的更改

标签 javascript jquery html

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

var $opts = $("#sel option").remove();
console.log($opts);
$("#sel").append($opts);
<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>


(或作为 fiddle )

它使选项具有值 "D"被选中,而不是具有值 "C" 的选项,如最初定义。注意控制台中打印的选项,属性 selected remove() 后更改方法。

为什么会这样?

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

最佳答案

迷人的问题。

我相信这取决于浏览器在删除唯一选定的选项时自动选择一个选项,然后当您将选项添加到具有 selected 的框中时会发生什么。标志设置。它似乎也是特定于浏览器的,这可能不会让我们感到惊讶。但更重要的是,Chrome 会根据我们观察的密切程度改变其行为。这确实让我感到惊讶。详情如下。

首先,让我们使用类似于您更新的示例的内容,但是我们从 selected 开始的那个既不是第一个也不是最后一个,然后看看我们在哪里结束。所以我们将有 A、B、C、D 和 E,并从选择 B 开始:

$("input[type=button]").on("click", function() {
  var $opts = $("#sel option").remove();
  $("#sel").append($opts);
});
<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>


我们观察到的结果因浏览器而异:
  • Chrome 最终出现在 C 上,这是一开始选择的一个(不是最后一个)。
  • IE11 最终在 B 上,也就是说,当我们删除/添加时,它似乎没有更改选定的标志。
  • Firefox 最终出现在 E(最后一个)上。

  • 更糟糕的是,Chrome 的行为取决于我们是否观察到 selected元素的属性,因为这是否发生。海森堡!如果我们观察它,它最终会做 Firefox 所做的(E)而不是 C。

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

    IE11

    看起来 IE11 只是不理会这些标志,可能是因为它不担心在 JavaScript 代码运行时更新它们,只有当它返回并且 IE 更新其 UI 时才会更新它们。

    火狐

    如果 Firefox 主动设置 selected,它就有意义。当框中未选择任何选项时在选项上标记(因为我们删除了选定的选项):
  • 我们去掉A,B保持选中状态。
  • 我们移除 B,C 被选中,因为 B 不再在框中,并且该框需要至少有一个被选中的选项。已删除选项的标志保持设置,这一点很重要
  • 当我们删除 C 和 D 时,重复步骤 2。
  • 我们移除 E。此时,C、D 和 E 都设置了标志。
  • 我们将 A 添加到框中。因为它是盒子唯一的选项,所以它得到了 selected标志设置。
  • 我们将 B 添加到框中。由于B的selected标志被设置,它成为选定的属性,A 被取消选择。
  • 当我们添加 C、D 和 E 时,重复步骤 6。
  • 我们最终选择了 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");
    }
    th, td {
      min-width: 3em;
      text-align: center;
      padding: 4px;
    }
    table, th, td {
      border-collapse: collapse;
      border: 1px solid #eee;
    }
    .label {
      text-align: left;
    }
    <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>


    Chrome 合金

    这给我们带来了困难:Chrome。 Chrome 会根据我们是否观察到 selected 来改变其行为。我们去的时候属性(property)。具体来说,如果我们观察 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");
      }
    });
    th, td {
      min-width: 3em;
      text-align: center;
      padding: 4px;
    }
    table, th, td {
      border-collapse: collapse;
      border: 1px solid #eee;
    }
    .label {
      text-align: left;
    }
    <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>


    如果你试一试(显然在 Chrome 上),你会注意到无论你对第一个或最后一个复选框做什么,你最终都会在 C 上,但是选中中间的复选框会让你最终在 E 上。

    这为我们提供了足够的数据来推断解释。我真的不能说 解释,只是它符合可用数据。如果有人想要更彻底,他们可以在调试版本中逐步浏览 Chrome 的内部代码。我认为这太过分了。

    所以这是我的答案,我再次强调推断:
  • Chrome 设置 selected HTMLOptionElement 上的属性(property)实例懒洋洋地 .也就是说,如果您不读取该属性的值,Chrome 不会检查以确保该属性反射(reflect)当前状态。
  • 它读取 selected排队时的属性 change事件。
  • 它不会排队 change事件,如果该选择框已经挂起。

  • 基于此,我认为 Chrome 会发生以下情况:
  • 我们去掉A,B保持选中状态。否 change事件,因为值没有改变。
  • 我们移除 B,C 被选中,因为 B 不再在框中,并且该框需要至少有一个被选中的选项。这将排队 change事件,观察 selected并因此在 C 上设置属性。
  • 我们删除了 C。 D 被选中(在某种意义上),但因为有一个 change事件待处理,无新 change事件已排队,因此 D 的 selected未观察到且其值未更新;它仍然false即使 如果 我们阅读它,它会变成true .
  • 删除 D 时再次相同。没有变化事件,没有观察。
  • 我们最终得到了具有 selected 的选项元素如 true在 B 和 C 上,但其他都没有,因为 D 和 E 在被选中时没有被观察到,并且没有更新它们的值。我们知道这些是他们的值(value)观,因为我们在那个时候被允许观察他们。
  • 我们按顺序添加元素,与 Firefox 一样,添加具有 selected = true 的元素。当另一个元素已被选择时,取消选择前一个元素。所以在添加 C 时取消选择 B。当我们添加 D 和 E 时,C 被保留下来,因为它们的 selected属性是 false .

  • ,这很有趣。

    关于javascript - 从选择中删除选项并重新附加选定的更改,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32782564/

    相关文章:

    javascript - Backbone.js 模型,指定属性

    javascript - 在 cypress.env.json 中定义变量

    javascript - 复选框切换所有与 jquery 触发器功能

    JQuery悬停功能无法正常工作

    html - 如何定义合理的 sass 占位符

    javascript - HTML5 中自定义数据属性名称中的有效字符

    html - CSS |匹配行内的动态 div 高度

    javascript - 自动完成代码?

    jquery - 选择最后动态添加的文本框

    javascript - 使用 Flow.js 在 React 中自动完成自定义按钮组件的 vs-code