关于JavaScript阵列的有趣事实
#javascript #编程 #arrays #fundamentals

javaScript有很多怪癖和怪异的部分,其中一些影响了普通网络开发人员的日常工作,而其中一些则属于那种黑暗艺术,这是唯一的实际应用是创建棘手的面试问题。尽管如此,值得一提的是一种语言。

今天,我正在考虑涵盖一些涉及JavaScript的阵列的鲜为人知的行为,所以让我们潜入。

数组实际上是对象

我们知道卷曲括号({})代表对象和方括号([])代表数组,对吗?
事实证明,数组只是一种特殊的对象。您可以通过检查数组的类型来证明这一点。

const dogBreeds = ["Labrador", "Poodle", "German Shepherd"]
console.log(typeof dogBreeds) // object

或一个更好的选择是在浏览器的控制台中注销一个数组,并看到koude2指向内置的koude3对象。

Screenshot from a JavaScript console showing an Array prototype

观察您所知道的和喜欢在原型对象上列出的所有内置阵列方法(它们仅阅读的褪色颜色信号)。

如果您想知道一些未知的数据确实是一个数组,那么最简单的方法是在Array原型上使用isArray static方法。

const somethingThatMightBeAnArray = ["dog", 42, {foo: 'bar'}]

console.log(Array.isArray(somethingThatMightBeAnArray)) // true

const somethingThatsNotAnArray = { id: 1 }

console.log(Array.isArray(somethingThatsNotAnArray)) // false

现在,当您使用括号符号(意思是myArray[0])访问阵列内部的项目时,您实际上是在访问对象的键,但是由于对象键不能为整数,在hood js下,实际上会转换该数字您将括号之间放在字符串之间。因此,实际上myArray[0]myArray["0"]会产生相同的结果。

还请注意,点表示法无法正常工作,因此myArray.0会失败,就像尝试使用DOT符号以获取使用"-"字符或整数的对象键的值一样。

const myObject = {"my-key": "A", "20": "B"};
console.log(myObject.20) // Uncaught SyntaxError: missing ) after argument list

console.log(myObject["20"]) // "B"

说到钥匙,或者在这种情况下我们称其为数组索引 - 还有很多人不知道的另一种奇怪行为。也就是说,如果您设置了具有比当前可用索引大的索引的项目,则JS将“填充”带有空值的该索引的项目。不相信我吗?自己尝试!

const dogBreeds = ["Labrador", "Poodle", "German Shepherd"]
dogBreeds[100] = "Vizsla"
console.log(dogBreeds.length) // 101

观察指数2和100之间的所有97个项目都将给予undefined

我真的不知道这是确切的原因,但是如果我不得不猜测这与与引擎盖下的JavaScript引擎下方存储在其他一些数据结构中的事实有关的事实有关。

实用的含义是,如果您想将新项目动态插入数组,则应始终使用内置的koude12方法,而不是括号符号,如果您想安全并保持索引的一致性。

数组通过参考存储

有些东西可以介绍初学者,有时甚至是其他语言的经验丰富的程序员。

试图比较看起来与您遇到惊喜的数组时。

const arr1 = [1, 2, 3]
const arr2 = [1, 2, 3]

console.log(arr1 === arr2); // false

这似乎很奇怪

在其他编程语言中,这是正确的。在php中说

$arr1 = [1, 2, 3];
$arr2 = [1, 2, 3];

var_dump($arr1 === $arr2); // true

或python。

from array import*

a = array("i", [1, 2, 3])
b = array("i", [1, 2, 3])

print(a == b) # True

那么这里发生了什么?如果您了解数组实际上是对象,那么整个过程将是有道理的。

在JavaScript对象中,reference存储,而不是像原始数据类型那样的值。因此,这意味着即使值(和键)是相同的,在内部,它们也存储在不同的内存中,因此,在比较它们以均等时,它们将always return koude13,除非您将相同的对象参考与自身进行比较。

现在再次具有现实世界的影响。例如,如果您只是将现有数组重新分配到新变量,然后开始突变您所处的值,以获得一些惊喜。

const dogBreeds = ["Poodle", "Labrador"]
const copiedDogBreeds = dogBreeds

copiedDogBreeds.push("Labradoodle")

console.log(copiedDogBreeds[2]) // "Labradoodle"
console.log(dogBreeds[2]) // "Labradoodle"

您最初希望您只修改了copiedDogBreeds数组,但是您可以看到我们的原始数组也将koude15作为其第三个项目。

要解决此问题,您需要进行对象的实际副本。一种方法是在没有任何参数
的情况下使用slice阵列方法

const dogBreeds = ["Poodle", "Labrador"]
const copiedDogBreeds = dogBreeds.slice()

copiedDogBreeds.push("Labradoodle")

console.log(copiedDogBreeds[2]) // "Labradoodle"
console.log(dogBreeds[2]) // undefined

或更好地使用spread syntax

const dogBreeds = ["Poodle", "Labrador"]
const copiedDogBreeds = [...dogBreeds]

copiedDogBreeds.push("Labradoodle")

console.log(copiedDogBreeds[2]) // "Labradoodle"
console.log(dogBreeds[2])

可是等等!还有更多

因此,您会认为上面的示例创建了一个全新的JavaScript array 对象,对吗?错误的!这两种方法仅创建一个so-called浅副本,该副本仍然共享源对象的基础值。

现在的实际结果是,如果我们修改任何嵌套值,那么我们仍将覆盖原始数组中的值。

const numbersAndLetters = [[1, 2, 3], ["a", "b", "c"]]
const copiedNumbersAndLetters = [...numbersAndLetters]

copiedNumbersAndLetters[1][0] = "A"

console.log(numbersAndLetters[1][0]) // "A"
console.log(copiedNumbersAndLetters[1][0]) // "A"

唯一的方法是制作一个deep copy,这是 - 您猜对了 - 一个全新的 array 对象,没有任何连接与原始连接。
最简单的方法是将其序列化至JSON字符串,然后将其解析为JavaScript对象。

const numbersAndLetters = [[1, 2, 3], ["a", "b", "c"]]
const copiedNumbersAndLetters = JSON.parse(JSON.stringify(numbersAndLetters))

copiedNumbersAndLetters[1][0] = "A"

console.log(numbersAndLetters[1][0]) // a
console.log(copiedNumbersAndLetters[1][0]) // A

概括

本质上,您需要记住,JavaScript中的数组实际上是索引作为其键的对象,作为对象,它们是通过参考存储的。

我希望您在阅读本文时学到了一两件事,因为我肯定会写它。