类数组本质上是一个对象,只是这个对象有点特殊,可以像数组一样使用。
组成部分
对象的属性名要为索引(数字)
必须要有length属性
例如下面的结构:
const obj = {
0: 'apple',
1: 'banana',
length: 2,
}

这样就是一个类数组,这个类数据具有类数组的必备条件:索引和length
添加方法
既然类数组是一个对象,那么它就可以拥有属性和方法,接下来给它添加一个数组push的方法
const obj = {
0: 'apple',
1: 'banana',
length: 2,
push: Array.prototype.push,
}

可以看到,添加后的确是往对象中push了一个orange,并且长度变成了3
但是,这样看起来和数组还是有点区别,数组是[]包着的,但类数组是用{}包着的,此时我们给类数组添加一个splice方法
const obj = {
0: 'apple',
1: 'banana',
length: 2,
push: Array.prototype.push,
splice: Array.prototype.splice,
}
const arr = []
console.log(obj)
console.log(arr)

添加了splice方法后,在浏览器控制台中打印类数组,类数组和真正的数组已经非常相似了,不过它还是一个对象,也可以当成数组来用,但是方法需要自己添加。
补充
为什么添加了splice方法后,在浏览器控制台打印类数组,会和真正的数组很相似呢?
翻阅github上的ChromeDevTools下的devtools-frontend源码发现

经过查阅,发现在浏览器开发者工具中,是通过判断obj中是否存在splice方法进行判断
如果存在splice方法,并且if语句内的条件成立,就返回true判断为是数组,所以会按照数组的样子打印
以下是if语句中的判断条件测试
const len1 = 0
len1 >>> 0 === len1 && (len1 > 0 || 1 / len1 > 0)
const len2 = 99
len2 >>> 0 === len2 && (len2 > 0 || 1 / len2 > 0)

经过测试,发现只要length长度为0或正整数,就返回true,数组也不会有长度为负数的时候,所以,浏览器控制台判断是否是数组的方式就是判断是否存在splice方法和length是否是0或正整数,两个条件都满足,那么浏览器就会以数组的形式打印。
小测试
const obj = {
2: 'apple',
3: 'banana',
4: 'orange',
length: 3,
push: Array.prototype.push,
splice: Array.prototype.splice,
}
obj.push('pear')
console.log(obj);
结果会打印什么呢?
要想得出答案,需要知道push是怎么添加元素到数组的
push方法
This method performs the following steps when called:
1. Let O be ? ToObject(this value).
2. Let len be ? LengthOfArrayLike(O).
3. Let argCount be the number of elements in items.
4. If len + argCount > 253 - 1, throw a TypeError exception.
5. For each element E of items, doa. Perform ? Set(O, ! ToString(𝔽(len)), E, true).
b. Set len to len + 1.
6. Perform ? Set(O, "length", 𝔽(len), true).
7. Return 𝔽(len).
摘自:https://tc39.es/ecma262/multipage/indexed-collections.html#sec-array.prototype.push
随后又去mdn文档查阅了一下,以下是mdn文档的解释
在非数组对象中使用 push()
push() 方法会读取 this 的 length 属性。然后,它将 this 的每个索引从 length 开始设置,并将参数传递给 push()。最后,它将 length 设置成之前的长度加上已添加元素的数量。
引自:https://developer.mozilla.org/zhCN/docs/Web/JavaScript/Reference/Global_Objects/Array/push
根据以上内容,略加修改,得出以下逻辑
Array.prototype.push = function (...rest) {
const len = this.length
for (let i = 0; i < rest.length; i++) {
this[len + i] = rest[i]
}
let newLengtn = len + rest.length
this.length = newLengtn
return newLengtn
}
这里以一个空数组,push一个元素来模拟
Array.prototype.push = function (...rest) {
// 假设是一个空数组,push一个元素
const len = this.length // => 0 (原来是空数组,所以长度是0)
const restLen = rest.length // => 1(push一个元素,所以参数的长度是1)
for (let i = 0; i < restLen; i++) {
// 每次循环将得到的数组长度加上循环的圈数(i)
// 相当于从原本的长度开始添加
// 也就是从当前数组的length开始添加
// this[0] = rest[0]
this[len + i] = rest[i]
}
// 将原来的长度加上参数的长度
// 所以就算原来数组长度是0,push一个参数之后,长度会变成1
// 可能这就是数组的length会比元素多1吧
let newLengtn = len + restLen // 0 + 1
// 将新长度覆盖给原来的长度
this.length = newLengtn // 1
// 返回新的长度
return newLengtn
}
简化一下,我们假设每一次push都是只push一个参数,那么上面的代码就变成了push的时候将length对应的设置为传入的参数,然后length+1就可以了
Array.prototype.push = params => {
obj[obj.length] = params
obj.length += 1
}
题解
看完上面的push过程,应该已经了解了push是如何将元素添加到数组里的
接下来返回刚刚的小测试
const obj = {
2: 'apple',
3: 'banana',
4: 'orange',
length: 3,
push: Array.prototype.push,
splice: Array.prototype.splice,
}
obj.push('pear')
console.log(obj);
现在重新看这个小测试,他的length是3,那么push就会找length对应的索引进行操作,将属性名(索引)为3的值改为pear(也就是banana被替换为pear),同时length+1变成4

此时如果在push一个mango,那么会将orange替换为mango,并且length变为5

以上就是我对有关类数组的总结理解,如有错误,欢迎批评指正!!

本站资源多数存于云盘,如有失效请邮件联系我们,我们将迅速处理。
本网站的文章部分内容可能来源于网络,如有侵权,请联系service#asuu.cn进行删除处理。
暂无评论内容