javaScript有很多怪癖和怪异的部分,其中一些影响了普通网络开发人员的日常工作,而其中一些则属于那种黑暗艺术,这是唯一的实际应用是创建棘手的面试问题。尽管如此,值得一提的是一种语言。
今天,我正在考虑涵盖一些涉及JavaScript的阵列的鲜为人知的行为,所以让我们潜入。
数组实际上是对象
我们知道卷曲括号({}
)代表对象和方括号([]
)代表数组,对吗?
事实证明,数组只是一种特殊的对象。您可以通过检查数组的类型来证明这一点。
const dogBreeds = ["Labrador", "Poodle", "German Shepherd"]
console.log(typeof dogBreeds) // object
或一个更好的选择是在浏览器的控制台中注销一个数组,并看到koude2指向内置的koude3对象。
观察您所知道的和喜欢在原型对象上列出的所有内置阵列方法(它们仅阅读的褪色颜色信号)。
如果您想知道一些未知的数据确实是一个数组,那么最简单的方法是在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中的数组实际上是索引作为其键的对象,作为对象,它们是通过参考存储的。
我希望您在阅读本文时学到了一两件事,因为我肯定会写它。