Skip to content

JavaScript学习之类数组

类数组本质上是一个对象,只是这个对象有点特殊,可以像数组一样使用。

组成部分

对象的属性名要为索引(数字)

必须要有length属性

例如下面的结构:

js
const obj = {
  0: 'apple',
  1: 'banana',
  length: 2,
}

JavaScript学习之类数组插图

这样就是一个类数组,这个类数据具有类数组的必备条件:索引和length

添加方法

既然类数组是一个对象,那么它就可以拥有属性和方法,接下来给它添加一个数组push的方法

js
const obj = {
  0: 'apple',
  1: 'banana',
  length: 2,
  push: Array.prototype.push,
}

JavaScript学习之类数组插图1

可以看到,添加后的确是往对象中push了一个orange,并且长度变成了3

但是,这样看起来和数组还是有点区别,数组是[]包着的,但类数组是用{}包着的,此时我们给类数组添加一个splice方法

js
const obj = {
  0: 'apple',
  1: 'banana',
  length: 2,
  push: Array.prototype.push,
  splice: Array.prototype.splice,
}
const arr = []
console.log(obj)
console.log(arr)

JavaScript学习之类数组插图2

添加了splice方法后,在浏览器控制台中打印类数组,类数组和真正的数组已经非常相似了,不过它还是一个对象,也可以当成数组来用,但是方法需要自己添加。

补充

为什么添加了splice方法后,在浏览器控制台打印类数组,会和真正的数组很相似呢?

翻阅github上的ChromeDevTools下的devtools-frontend源码发现

JavaScript学习之类数组插图3

经过查阅,发现在浏览器开发者工具中,是通过判断obj中是否存在splice方法进行判断

如果存在splice方法,并且if语句内的条件成立,就返回true判断为是数组,所以会按照数组的样子打印

以下是if语句中的判断条件测试

js
const len1 = 0
len1 >>> 0 === len1 && (len1 > 0 || 1 / len1 > 0)
const len2 = 99
len2 >>> 0 === len2 && (len2 > 0 || 1 / len2 > 0)

JavaScript学习之类数组插图4

经过测试,发现只要length长度为0或正整数,就返回true,数组也不会有长度为负数的时候,所以,浏览器控制台判断是否是数组的方式就是判断是否存在splice方法和length是否是0或正整数,两个条件都满足,那么浏览器就会以数组的形式打印。

小测试

js
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方法

js
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

根据以上内容,略加修改,得出以下逻辑

js
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一个元素来模拟

js
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就可以了

js
Array.prototype.push = params => {
  obj[obj.length] = params
  obj.length += 1
}

题解

看完上面的push过程,应该已经了解了push是如何将元素添加到数组里的

接下来返回刚刚的小测试

js
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

JavaScript学习之类数组插图5

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

JavaScript学习之类数组插图6

艾雨博客微信公众号二维码
© 版权声明
文章版权归作者所有,未经允许请勿转载.
本站资源多数存于云盘,如有失效请邮件联系我.
本网站的文章部分内容可能来源于网络,如有侵权,请联系884684993#qq.com进行删除处理.