我做了一个嘘声,填充了一个阵列
#javascript #发展 #bug

前几天,我正在与一个年轻的开发人员聊天,他们提到观看教程和阅读博客文章对他们来说很难,因为作者似乎总是把他们的东西放在一起。他们确切地知道要添加的内容,何时添加,它将如何工作等等。但是,当我尝试构建同一件事时,我只是遇到了一个问题,这需要我5倍,而我只是不知道t获得这些人如何在不遇到我遇到的问题的情况下工作。

自然,我回答说他们只能看到最终结果。创作者在创建内容时遇到了很多问题,在100个中,有99次遇到了很多问题,但他们只展示了在解决这些内容后起作用的东西。这对他们来说似乎很有意义,但是关于我的谈话困扰着我。

几天后,我受到了一些灵​​感……如果我分享了每个开发人员的错误和错误是正常的吗?,所以我们在这里!

这是一系列新帖子的开始,我将分享我在编码某些内容时犯的一些骨头错误。我希望它能阐明“现实世界中的事情”,即使对于拥有多年经验的开发人员也是如此。希望我能在此过程中教几件事,但是最后,我真的只是想证明我们都在代码中犯了错误。他们中的一些人会很明显,其中一些可能很复杂……但是它们发生在每个人身上...从我到世界上最好的开发者。

这样,让我与你分享一个我昨天犯的错误...

Will Arnet saying "I've made a huge mistake".

前提

我目前正在使用三js进行一个项目,我需要在我的场景中添加一堆立方体。 (如果您不知道三j,则不必担心您不需要跟随)自然而然地,而不是手动添加它们,而是使用for for循环自动创建它们。然后,当我四处移动相机时,我想在相机接近每个相机时将它们动画一次。为此,我需要跟踪我所有的立方体,以及旗帜,以指示它们是否是动画的。我决定使用看起来像这样的对象:

interface ICube {
  /**
   * this is a ThreeJS thing. But you
   * can consider it like any other object
   * in Javascript for the sake of this
   * post.
   */
  mesh: THREE.Mesh;
  animated: boolean; 
}

我认为我会在前面填充数组,然后在以后创建后添加网格(对象)。因此,我声明了数组并像这样填充了:

const NUM_CUBES = 10;

const cubes: ICube[] = Array(NUM_CUBES).fill({
  mesh: null,
  animated: false,
})

所以现在我有10个立方体,如果或多或少需要,我可以更改NUM_CUBES ... Easy Peazie。

稍后,在创建场景之后,我创建了立方体,将它们添加到场景中,然后将它们添加到数组中。

const createCubes = () => {
  for (let i = 0; i < cubes.length; i++) {
    // ...create the cube in ThreeJS and add to scene

    cubes[i].mesh = mesh;
  }
}

接下来,我致力于移动相机,最后我想在相机接近每个相机时将动画添加到立方体。

const animate = () => {
  for (let i = 0; i < cubes.length; i++) {
    const cube = cubes[i];

    // skip this cube if it has already been animated.
    if (cube.animated) continue;

    if (camera.position.distanceTo(cube.position) < 10) {
      // animate

      // if animation complete
      cube.animated = true;
    }
  }
}

A monkey pushing a laptop off a table

全部完成,时间进行测试。

有点不对

立即出现了问题。我接近的第一个立方体不会动画。我仔细检查了我的逻辑和操作顺序。一切似乎都还好。如果我删除了动画函数中的条件逻辑,则动画可以工作。

一些日志的时间...

...是否存在该立方体?是的...
...是否已经标记为动画?不...
...相机和立方体之间的距离是多少? 30?!有趣的...相机就在立方体旁边,该值必须比那少!
...相机的位置是什么?好的,看起来正确...
...第一个立方体的位置是什么? “ Z:40”?!好吧,那是不对的...第一个立方体应该在位置0 ...
...最后一个立方体的位置是什么? “ Z:40”?!嗯...他们的位置相同...那绝对不对。在场景中,它们没有在相同的位置显示,因此它们被正确渲染。我的数组一定是东西...
...所有立方体的位置是什么? “ Z:40”。 “ Z:40”,“ Z:40” ...怪异...他们不应该全部相同...也许当我创建Cube Meshes时我搞砸了...

回到CreateCubes()

...每个网格被添加到数组之前的位置是什么? “ z:0”,“ z:3”,“ z:6” ...看起来正确。
...设置每个网格后的数组是什么样的?

// first iteration
cubes: [
  { ..., z: 0 }
]

// second iteration 
cubes: [
  { ..., z: 3 },
  { ..., z: 3 }
]

// third iteration 
cubes: [
  { ..., z: 6 },
  { ..., z: 6 },
  { ..., z: 6 }
]

Ben from Parks and Recreation thinking at a desk, then someone holds a lightbulb over his head and turns it on. Ben gestures as though he has just figured something out.

就在那里!设置网格时,它正在更改所有对象...通常意味着参考问题。

通过参考存储

在JavaScript中,对象通过参考存储在变量中。这意味着对象数据存储在内存中,并且对内存中的地址的引用存储在变量中。

假设我们将对象分配给变量,然后将该变量的值分配给另一个变量

const myObject = { x: 123, y: 456 };

const myOtherObject = myObject;

在这种情况下,myObjectmyOtherObject均参考内存中的相同地址。实际数据尚未重复。数据仍然存在于记忆中的一个地方。因此,如果我们使用myObject更改x的值。

myObject.x = 789;

内存中的数据被更新。然后,如果我们使用myOtherObject读取数据,它将在刚刚更新的内存中引用相同的地址...

console.log(myOtherObject.x); // 789

An animated cat spreading it's arms and the words "The More You Know" appear over it's head.

因此,我数组中的所有对象都指内存中的同一对象。但是为什么?

当我声明我的立方体数组...
时,这一切都可以追溯到

const cubes: ICube[] = Array(NUM_CUBES).fill({
  mesh: null,
  animated: false,
});

传递到.fill()的对象仅在内存中创建一次,然后将内存中该对象的地址应用于我数组中的每个元素。因此,就像上面的示例一样,如果使用其中一个元素来更新内存中的数据,它们都读取相同的数据

// first element is updated
cubes[0].mesh.position.z = 999;

// second element reads the same data in memory, so it was too!
console.log(cubes[1].mesh.position.z) // 999;

我一开始就没有想到这一点,只是假设.fill()会将一个新对象应用于数组中的每个元素。

Jason Momoa posing adorably with the words "Oopsie Daisy" displayed.

解决方案

现在我知道问题了,解决方案很简单...

我将立方体数组的实例化更改为一个空数组...

const cubes: ICube[] = [];

然后在createCubes()内部,当创建每个立方体时,我将一个新对象推向数组。

const createCubes = () => {
  for (let i = 0; i < cubes.length; i++) {
    // ...create the cube in ThreeJS and add to scene

    cubes.push({ mesh, animated: false });
  }
}

现在,每次都会在内存中创建一个新对象,并且数组中的每个元素现在都是唯一的。在内存中不再引用相同的地址。

我再次测试和BAM!当相机靠近它时,每个立方体都会动画起来!

Carlton Banks from Fresh Prince of Bel-Air doing his happy dance.

结论

即使我们(希望)在JavaScript的基本面上学习的量和参考存储是我们仍然忽略的。

如果您立即抓住了这个问题,那就太棒了!如果没有,就不用了...我也没有。但是没关系!我在此过程中学到了一些东西,并且将来会留意它。

我希望这表明,即使经过多年的编写代码,您仍然可以犯简单的错误。您仍将学习一些可能很久以前学到的新知识。预计。这是正常的。最重要的是,没关系。

感谢您允许我与您分享我的错误。直到下一次,Happy Hacking!