使用Lodash克隆的两个数组,彼此独立但相同。为什么?

4

我知道有一些非常明显的东西我完全没有注意到,所以非常感谢您的帮助。

我有一个功能,提供两个下拉列表。它们包含相同的数据(该功能允许两个人之间进行交易,这些人就是数据),但我希望它们各自获得自己的数据副本。

该功能的另一个部分是,通过在第一个下拉列表中选择A人,我希望禁用第二个下拉列表中的A人,反之亦然,因此我让ng-options标签注意对象上的disabled属性。

我的问题是,即使使用诸如Lodash的clone方法来正确创建“新”数组,每次我仅访问一个数组中的A人(并明确不访问另一个数组)时,我总是看到当我触摸A人时,该对象在两个数组中都被更新,这让我感到困惑。

这感觉像是一个基于Javascript的低级问题(标准的PEBCAK,我感觉我显然误解或完全遗漏了某些基本内容),也许涉及一些AngularJS渲染相关的乐趣,但是...出了什么问题?

angular.module('myApp', [])
  .controller('weirdDataController', function($scope) {
    $scope.$watch('manager1_id', () => {
      if (angular.isDefined($scope.manager1_id) && parseInt($scope.manager1_id, 10) > 0) {
        $scope._disableManagerInOtherDropdown(false, $scope.manager1_id);
      }
    });

    $scope.$watch('manager2_id', () => {
      if (angular.isDefined($scope.manager2_id) && parseInt($scope.manager2_id, 10) > 0) {
        $scope._disableManagerInOtherDropdown(true, $scope.manager2_id);
      }
    });

    $scope._gimmeFakeData = () => {
      return [{
          manager_id: 1,
          manager_name: 'Bill',
          disabled: false
        },
        {
          manager_id: 2,
          manager_name: 'Bob',
          disabled: false
        },
        {
          manager_id: 3,
          manager_name: 'Beano',
          disabled: false
        },
        {
          manager_id: 4,
          manager_name: 'Barf',
          disabled: false
        },
        {
          manager_id: 5,
          manager_name: 'Biff',
          disabled: false
        },
      ];
    };

    const data = $scope._gimmeFakeData();
    $scope.firstManagers = _.clone(data);
    $scope.secondManagers = _.clone(data);

    $scope._disableManagerInOtherDropdown = (otherIsFirstArray, managerId) => {
      const disableManagers = manager => {
        manager.disabled = manager.manager_id === managerId;
      };

      if (otherIsFirstArray) {
        $scope.firstManagers.forEach(disableManagers);
      } else {
        $scope.secondManagers.forEach(disableManagers);
      }

      console.log('Is the first item the same??', $scope.firstManagers[0].disabled === $scope.secondManagers[0].disabled);
    }
  });
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.7.5/angular.min.js"></script>
<div ng-app="myApp" ng-controller="weirdDataController">
  <div class="col-xs-12 col-sm-6">
    <select class="form-control" ng-model="manager1_id" ng-options="manager.manager_id as manager.manager_name disable when manager.disabled for manager in firstManagers track by manager.manager_id">
      <option value="" disabled="disabled">Choose one manager</option>
    </select>
    <select class="form-control" ng-model="manager2_id" ng-options="manager.manager_id as manager.manager_name disable when manager.disabled for manager in secondManagers track by manager.manager_id">
      <option value="" disabled="disabled">Choose another manager</option>
    </select>
  </div>
</div>
<br /><br />

为了让它能工作并说明问题,我将与 $scope 相关的所有内容都丢了进去。下面是具体流程:
  • 初始化时,我获取数组,然后为每个下拉列表克隆一份副本。
  • 当每个下拉列表更改模型属性(对象 ID)时,我使用一个 scope 监听器调用一个方法来处理在相反列表中禁用所选对象/人员。
  • 在这个方法内部,我确定要迭代哪个列表/数组,并标记禁用的对象。
  • 在该方法结束时,我会进行一个简单的 console.log 调用以检查给定对象的值。出于快速和方便起见,我只获取索引为 0 的项目。
  • 我的期望:一个对象的 disabled 值为 true,而相反的对象的值为 false。但实际看到的是两者的值都为 true(假设选择了下拉列表中的第一个“真实”项目)。
问题出在哪里?我到底有多蠢?

4
好的,你需要 _.cloneDeep() - Patrick Roberts
那就是我犯了大错的部分。我知道深度克隆,但认为它只在我所处的一层之下,并且假设带有“简单”对象的数组会没问题?我不知道我为什么这样想,但那就是我的想法。我已经从中吸取了教训。请提供答案,我会标记为正确,您先生是赢家 :) - Mattygabe
2
仅作为旁注,我认为Patrick在这里说得很对,虽然cloneDeep解决了问题,但问题最终成为了代码中的一种怪味。我回过头来重新思考了一下功能的设计(是我自己4年前设计的?),并相应地进行了更新,以更好地处理UI和底层数据之间的分离。感谢大家的帮助! - Mattygabe
是的,我开始写一个类似的答案,但意识到需要比我最初想象的更多的工作。我不想给你留下一个次优的解决方案或者一个关于更好方法的半成品想法,所以我选择放弃了。不过,我很高兴你能自己解决它。 - Patrick Roberts
1
@Mattygabe,如果您自己发布一个答案,那么这个问题就会有答案,这将非常有用;回答自己的问题是完全可以的。 - trincot
显示剩余3条评论
1个回答

0
我的问题的答案是:clone()默认情况下不执行“深度”克隆,因此尽管我错误地尝试了不同的方法,但我仍在处理相同的数组。使用 Lodash 的 cloneDeep() 方法解决了我的问题,但正如 Patrick 建议的那样,我重新评估了我编写的有问题的方法并重构了它,这样我就不需要使用任何克隆了。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接