前几天,我正在与一个年轻的开发人员聊天,他们提到观看教程和阅读博客文章对他们来说很难,因为作者似乎总是把他们的东西放在一起。他们确切地知道要添加的内容,何时添加,它将如何工作等等。但是,当我尝试构建同一件事时,我只是遇到了一个问题,这需要我5倍,而我只是不知道t获得这些人如何在不遇到我遇到的问题的情况下工作。
自然,我回答说他们只能看到最终结果。创作者在创建内容时遇到了很多问题,在100个中,有99次遇到了很多问题,但他们只展示了在解决这些内容后起作用的东西。这对他们来说似乎很有意义,但是关于我的谈话困扰着我。
几天后,我受到了一些灵感……如果我分享了每个开发人员的错误和错误是正常的吗?,所以我们在这里!
这是一系列新帖子的开始,我将分享我在编码某些内容时犯的一些骨头错误。我希望它能阐明“现实世界中的事情”,即使对于拥有多年经验的开发人员也是如此。希望我能在此过程中教几件事,但是最后,我真的只是想证明我们都在代码中犯了错误。他们中的一些人会很明显,其中一些可能很复杂……但是它们发生在每个人身上...从我到世界上最好的开发者。
这样,让我与你分享一个我昨天犯的错误...
前提
我目前正在使用三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;
}
}
}
全部完成,时间进行测试。
有点不对
立即出现了问题。我接近的第一个立方体不会动画。我仔细检查了我的逻辑和操作顺序。一切似乎都还好。如果我删除了动画函数中的条件逻辑,则动画可以工作。
一些日志的时间...
...是否存在该立方体?是的...
...是否已经标记为动画?不...
...相机和立方体之间的距离是多少? 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 }
]
就在那里!设置网格时,它正在更改所有对象...通常意味着参考问题。
通过参考存储
在JavaScript中,对象通过参考存储在变量中。这意味着对象数据存储在内存中,并且对内存中的地址的引用存储在变量中。
假设我们将对象分配给变量,然后将该变量的值分配给另一个变量
const myObject = { x: 123, y: 456 };
const myOtherObject = myObject;
在这种情况下,myObject
和myOtherObject
均参考内存中的相同地址。实际数据尚未重复。数据仍然存在于记忆中的一个地方。因此,如果我们使用myObject
更改x
的值。
myObject.x = 789;
内存中的数据被更新。然后,如果我们使用myOtherObject
读取数据,它将在刚刚更新的内存中引用相同的地址...
console.log(myOtherObject.x); // 789
因此,我数组中的所有对象都指内存中的同一对象。但是为什么?
嘘
当我声明我的立方体数组...
时,这一切都可以追溯到
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()
会将一个新对象应用于数组中的每个元素。
解决方案
现在我知道问题了,解决方案很简单...
我将立方体数组的实例化更改为一个空数组...
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!当相机靠近它时,每个立方体都会动画起来!
结论
即使我们(希望)在JavaScript的基本面上学习的量和参考存储是我们仍然忽略的。
。如果您立即抓住了这个问题,那就太棒了!如果没有,就不用了...我也没有。但是没关系!我在此过程中学到了一些东西,并且将来会留意它。
我希望这表明,即使经过多年的编写代码,您仍然可以犯简单的错误。您仍将学习一些可能很久以前学到的新知识。预计。这是正常的。最重要的是,没关系。
感谢您允许我与您分享我的错误。直到下一次,Happy Hacking!