Array.prototype.entries()
官方解释:Array 实例的 entries()
方法返回一个新的数组迭代器对象,其中包含数组中每个索引的键/值对。
说实话,这个我暂时在项目中没有发现其应用场景,所以就直接贴出其用法及实例。
但是在学习这个数组方法的过程中还可以巩固一下相关的js知识,可以查缺补漏。
现在看下这个代码的打印结果
可以看到,它确实返回的是一个迭代器对象,当然了在使用中肯定不是看它的这个输出,而是看它里面的每个值或索引。
所以,在下面使用next()来访问这个迭代器,上个学习笔记中已经介绍了next(),这里就不再赘述。通过next()访问这个迭代器后,返回的也是一个对象,其包含两个属性:
done
: 一个布尔值,如果迭代器已经遍历完所有元素,则为true
;否则为false
。value
: 包含迭代器当前元素的值。
正常来说,第一个next().value就会对应着[0,'a']这样的输出,所以在下面我们再次调用next(时)就会对应着[1,'b']
再看一个实例,结合了for...of
输出结果如下
可以看出,第一种方式以结构的方式来遍历这个迭代器对象,输出的就是对应的索引和值。第二种方式是直接遍历这个迭代器对象,那么输出的就是每个数组了,从上面我们也可以看出,就对应着每个next().value的值。
此时突然有一个想法,在以前的学习中,总是记得for ..in和for ...of可以相互替换,但是也有区别,当时记得不太清楚,也就是抱着试一试的想法来进行一个替换试一试。
for..in的输出在81行,我们来到控制台看一下,
发现并没有输出,我查了一下for of和for ...in的区别,得到了答案:
第二个 for...in
循环不会按你期望的方式工作。for...in
循环是用来遍历对象的可枚举属性的,它会返回属性名(字符串类型)。当对 arr3.entries()
使用 for...in
时,你实际上是在尝试遍历迭代器对象自身的属性,而不是它产生的 [index, element]
对。
由于迭代器对象通常没有可枚举的自有属性(除了可能继承自原型链的属性),所以 for...in
循环在这里不会输出任何与数组元素相关的内容。它可能会输出迭代器对象自身的某些属性(如果有的话),或者根本不输出任何内容。
然后贴出for..in和for ...of的区别
for...of
- 遍历值:
for...of
循环用于遍历可迭代对象(如数组、字符串、Map、Set)的值。 - 不遍历原型链:它不会遍历对象的原型链上的属性。
- 可迭代协议:它依赖于对象的迭代器协议(具有
Symbol.iterator
函数的对象)。 - ** break、continue 和 return**:可以在循环中使用
break
、continue
和return
语句。
示例:
for (const value of [1, 2, 3]) { console.log(value); // 输出:1, 2, 3 }
for...in
- 遍历键:
for...in
循环用于遍历对象的所有可枚举属性(包括继承的枚举属性)的键。 - 遍历原型链:它会遍历对象的原型链上的可枚举属性,除非使用
hasOwnProperty
或其他方法来过滤。 - 枚举属性:它遍历的是对象的枚举属性,而不是通过迭代器协议。
- ** break、continue 和 return**:同样可以在循环中使用
break
、continue
和return
语句。
示例:
for (const key in {a: 1, b: 2, c: 3}) { console.log(key); // 输出:a, b, c }
主要区别
- 遍历的内容:
for...of
遍历的是值,而for...in
遍历的是键(属性名)。 - 原型链:
for...in
会遍历对象的原型链上的属性,而for...of
不会。 - 适用对象:
for...of
适用于可迭代对象,而for...in
适用于任何对象。 - 性能:由于
for...in
需要处理原型链,它通常比for...of
慢。
在选择使用哪种循环时,通常取决于你想要遍历的是对象的键还是值,以及是否需要考虑对象的原型链。当你想要避免遍历原型链上的属性时,for...of
循环是一个更好的选择。
简单来说,其实就是如果你要使用的是可以枚举的,像[1,2,3]或{a:1,b:2,c:3}这样的可视的数组或对象,那两者替换没有什么问题,如果要是出现迭代器对象,还有ES6+后新增的Map,Set这些,就要仔细斟酌一下两者的区别,来进行使用。
然后再举一个在伪数组对象上调用entries()的例子:
输出结果如下:
-
Array.prototype.entries.call(arrayLike)
:这里使用了call
方法来调用Array.prototype.entries
函数,并将arrayLike
作为this
的值传递给它。entries()
方法是Array.prototype
上的一个原型方法,它原本设计用来在数组实例上调用。然而,代码中的arrayLike
是一个类数组对象,而不是真正的数组实例。为了使entries()
方法能够在arrayLike
上工作,需要显式地将arrayLike
设置为this
值。 -
for (const entry of ...)
:这是一个for...of
循环,它遍历entries()
方法返回的迭代器。每次迭代都会获取一个包含两个元素的数组,第一个元素是当前属性的键(即索引),第二个元素是属性的值。 -
console.log(entry)
:在每次迭代中,打印当前键/值对数组。
注意,尽管 arrayLike
对象有一个索引为 3 的属性 "d"
,但是因为 length
属性的值是 3,entries()
方法只会迭代到索引 2。这意味着索引 3 的属性 "d"
不会被遍历到。
这里又涉及到伪数组或类数组的概念,就当复习了,把相关的解释也贴出来:
类数组(array-like object)不完全等同于一个真正的 JavaScript 数组实例。类数组对象具有以下特征:
-
具有
length
属性:类数组对象有一个length
属性,它的值是一个数字,表示对象中可以遍历的元素数量。 -
索引属性:类数组对象使用非负整数值作为属性名,这些属性名对应于对象的“元素”。
-
没有数组方法:与真正的数组不同,类数组对象通常不包含数组原型(
Array.prototype
)上的方法,如map
、filter
、forEach
、entries
等。
一些常见的类数组对象示例包括:
- 函数的
arguments
对象:在函数内部,arguments
是一个类数组对象,它包含了传递给函数的所有参数。 - HTMLCollection 和 NodeList:这些是 DOM API 返回的对象类型,它们表示文档中的元素集合,例如通过
document.getElementsByClassName
或document.querySelectorAll
返回的对象。
下面是一个简单的类数组对象的例子:
const arrayLike = { 0: 'first', 1: 'second', 2: 'third', length: 3 };
尽管类数组对象在语法上看起来像数组,但它们缺少数组的方法。为了使用数组的方法,比如 Array.prototype.forEach
或 Array.prototype.entries
,你需要将类数组对象转换为真正的数组,或者使用 Function.prototype.call
或 Function.prototype.apply
来借用数组的方法,就像之前的例子中那样。
以下是如何将类数组对象转换为真正的数组:
const trueArray = Array.prototype.slice.call(arrayLike);
在这个例子中,slice
方法被用来从类数组对象创建一个新数组。由于 slice
是数组原型上的一个方法,我们可以通过 call
来指定 arrayLike
作为 this
值,从而将类数组对象转换为数组实例。
Array.prototype.every()
Array 实例的 every() 方法用于测试数组中的所有元素是否都通过由提供的函数实现的测试。它返回一个布尔值。
every(callbackFn,thisArg)
第一个参数:接受一个回调函数
第二个参数:可选 在执行 callbackFn 时用作 this 的值。
every() 方法是一种迭代方法。它为数组中的每个元素调用一次提供的回调函数 callbackFn,直到 callbackFn 返回一个假值。如果找到这样的元素,every() 立即返回 false 并停止遍历数组。否则,如果 callbackFn 对所有元素都返回一个真值,every() 则返回 true。
常用的就是只有第一个参数的情况,下面以实例来演示:
执行结果如下:
上面的代码就是来验证数组中的每一个元素是否都是大于10的,如果都大于10,那就返回true,如果有一个不大于10,那就返回false。
再来一个例子,一般数组方法也会混用,下面就展示一个数组方法混用来实现一个数组是否包含另一个数组的实例。
执行结果为:
代码的意识为:定义了一个isSubset函数,该函数的返回值为传入的参数2调用every函数的结果,在every函数内,同样传入了一个回调函数,该回调函数的返回值为,传入的参数1是否包含参数2中的所有元素。
所以在第一行输出中,第二个参数中所有元素都在参数一种,所以返回为true。
第二行的输出则出现4不在参数1的数组中的情况,所以返回为false。
提示:这里也使用了一个includes函数,这里先不做详细介绍,后续会进行相关的更新,这里的作用就是看传入的参数中是否包含在调用它的这个数组中,如果存在,返回true,如果不存在,返回false。
还有一种用法就是利用回调函数的第三个参数,注意,这里是回调函数的第三个参数,而不是every()的第三个参数,而是第一个参数回调函数内部的第三个参数!!!
那就要介绍一下回调函数的参数吧:
-
单个参数: 当回调函数只需要处理单个值时,它通常只有一个参数,这个参数代表传递给回调的当前值。
function myCallback(value) { console.log(value); } [1, 2, 3].forEach(myCallback); // 输出:1, 2, 3
-
两个参数: 在许多数组方法中,回调函数接受两个参数:当前元素和其索引。
function myCallback(item, index) { console.log(`索引 ${index} 的值是 ${item}`); } [10, 20, 30].forEach(myCallback); // 输出: // 索引 0 的值是 10 // 索引 1 的值是 20 // 索引 2 的值是 30
-
三个参数: 在一些情况下,回调函数可能接受三个参数:当前元素、索引和原始数组。
function myCallback(item, index, array) { console.log(`当前数组是 ${array},索引 ${index} 的值是 ${item}`); } [1, 2, 3].forEach(myCallback); // 输出: // 当前数组是 1,2,3,索引 0 的值是 1 // 当前数组是 1,2,3,索引 1 的值是 2 // 当前数组是 1,2,3,索引 2 的值是 3
-
更多参数: 有时,回调函数可能需要处理额外的参数,这些参数可以是在调用回调时传递的额外参数。
function myCallback(item, extra1, extra2) { console.log(item, extra1, extra2); } [1, 2, 3].forEach((item) => myCallback(item, 'extra1', 'extra2')); // 输出: // 1 "extra1" "extra2" // 2 "extra1" "extra2" // 3 "extra1" "extra2"
所以,第三个参数就是原数组
请看实例:
那么就来解释一下这个代码,当时在第一次看这样的代码时有一些懵,直接.filter和.every是什么鬼,原来这个用回退键消除换行符就可以很清楚的看到,原来是链式调用!
首先,定义了一个数组,然后调用filter方法,这个暂时还没有详细介绍,就是数组的过滤方法,返回满足条件的新数组,此时返回的就是去掉所有负数的新数组。
然后,再使用every方法进行判断,首先将空数组返回为true,然后判断非空情况,就是将arr[]原数组中的索引前一项和当前项进行比较,就是递增比较,如果数组满足递增条件,返回为true,否则为false。
伪数组对象的判断:
执行结果为:
上面代码就是来判断伪数组对象中的元素值是不是字符串,length不做判断,以及定义了length为3,所以第四项345也不会进行判断。所以最终的返回结果为true。