哈哈 会爬树的金鱼,树上的金鱼呦😀
前言
面试官:说一下类数组和数组的区别?
我:类数组和数组一样啊。没区别
面试官:用document.querySelectorAll选择节点后可以用map或filter方法吗?
我:可以啊。forEach的可以用为啥map或filter不行?
面试官: 回答不错,回去等消息吧
我:
识别真数组😘
真假美猴王👻
类数组,顾名思义是类似数组的数据结构,它有数组的部分特征但又不是数组 类数组并不是真正的数组,所以不能直接使用数组的方法。可以用 Array.isArray 来判断是否是真数组
常见的类数组有:
-
Arguments对象:函数的参数列表,有length属性但不是真数组。
function foo() { console.log(arguments.length); // 3 console.log(Array.isArray(arguments)); // false } foo(1, 2, 3);
-
HTMLCollection对象:文档的节点列表,有length和可迭代性但不是真数组。
const elements = document.querySelectorAll('div'); console.log(elements.length); // 5 console.log(Array.isArray(elements)); // false
-
字符串:有length属性和可迭代性但不是真数组。
console.log('foo'.length); // 3 console.log(Array.isArray('foo')); // false
-
jQuery对象也不是个真数组
const $div = $('div') console.log($div.length); // 5 console.log(Array.isArray($div)); // false
实现个类数组😊
从石头里出来的不一定是美猴王,可能是六耳🙈
既然数组本身也是个对象,那么是不是可以用对象来创建个类似数组的结构呢?
一个数组基本要求需要满足两种条件
-
索引值 为正整数
-
有 length 属性并且值为正整数
那我们就可以用对象模拟个,毕竟数组也是对象嘛╰( ̄▽ ̄)╮
先来数组索引,然后添加 length 属性
哈哈 这个看上去是数组了,能用for方法遍历了
const arr = {
0:'树',
1:'上',
2:'的',
3:'金',
4:'鱼',
5:'呦',
6:'公',
7:'众',
8:'号',
length: 9
}
for(let i=0;i<arr.length;i++){
console.log(arr[i])
}
但我们还不支持forEach,和 map 方法 所以给他添加上
注意: 不能用箭头函数哦,this指向问题
const arr = {
0:'树',
1:'上',
2:'的',
3:'金',
4:'鱼',
5:'呦',
6:'公',
7:'众',
8:'号',
length: 9,
forEach(callback){
const arr = this;
for(let i=0;i<arr.length;i++){
callback(arr[i],i,arr)
}
}
}
arr.forEach((i)=>console.log(i)) // 树 上 的 金 鱼 ......
同理 把 map 和 filter 给加上
const arr = {
0:'树',
1:'上',
2:'的',
3:'金',
4:'鱼',
5:'呦',
6:'公',
7:'众',
8:'号',
length: 9,
forEach(callback){
const arr = this;
for(let i=0;i<arr.length;i++){
callback(arr[i],i,arr)
}
},
map(callback){
const arr = this;
const result = []
for(let i=0;i<arr.length;i++){
result.push(callback(arr[i],i,arr))
}
return result
},
filter(callback){
const arr = this;
const result = []
for(let i=0;i<arr.length;i++){
const value = callback(arr[i],i,arr);
value && result.push(arr[i]);
}
return result
}
};
arr.filter(i=>i === '鱼') // ['鱼']
arr.map((v,i)=>`值:${v},索:${i}`) // ['值:树,索:0', '值:上,索:1',...]
如果类数组实现了迭代器(iterable)那么可以用,ES6的展开语法,forof/in遍历的方法了.
注意:这个[Symbol.iterator]不能动态创建,必须用声明式,中途赋值无效
const arr = {
0:'树',
1:'上',
2:'的',
3:'金',
4:'鱼',
5:'呦',
6:'公',
7:'众',
8:'号',
length: 9,
[Symbol.iterator](){
let index = 0;
const that = this;
return {
next() {
if (index < that.length) {
return {
done: false,
value: that[index++]
};
} else {
return {
done: true
};
}
}
}
}
};
console.log([...arr]) /// ['树', '上', '的', '金', '鱼', '呦', '公', '众', '号']
就这样把所有的数组方法实现一遍就好了.也可以自定义哦
怕麻烦可以直接换__proto__,但换后还是类数组,虽然有所有数组方法
arr.__proto__ = Array.prototype;
Array.isArray(arr) // false
性能上区别🤦♂️
毕竟是山寨货跑不快很正常🦢
类数组毕竟不是真实的数组,方法是我们自己实现的,在大数据面前性能肯定没有原生效率高. 类数组和真实数组有以下几点区别:
- 存储方式不同。真实数组使用数字索引存储元素,而类数组使用对象的字符串键来存储,这会占用更多的内存空间。
- 连续的内存空间。真实数组的元素存储在连续的内存空间中,这使得查找和操作元素更高效。类数组的元素存储在对象的属性中,没有这种效率优势。
- 大小可变性。真实数组的大小是可变的,可以随意添加和删除元素。而类数组的大小通常是固定的,不能直接添加和删除元素,除非实现对应方法(push、pop、slice等)。
- 数组方法和属性。真实数组有丰富的数组方法(push、pop、slice等)和数组属性(length)可用,而类数组只有长度length属性,其他方法需要自己去实现。
- 迭代效率。由于真实数组元素在内存中是连续的,所以迭代效率更高。而类数组每个元素都需要通过对象属性查找,效率较低。
所以使真实数组在存储和性能方面都优于类数组:
- 占用更少内存,更省空间。
- 数组操作如查找、插入和删除元素更加高效。
- 支持数组全部方法和属性,使用更加灵活方便。
- 迭代效率更高,循环遍历更快速。
如果需要高性能地操作数据,还是建议将类数组转换为真实数组使用。当然,如果数据量很小,或只需简单操作,使用类数组也不会有太大问题。
类数组转真实数组🤭
偷梁换柱,假的变成真的,真的还是真的🦉
如何将类数组转真实数组呢?有这些方法:
-
Array.from():ES6新增方法,可以将类数组对象转为真实数组。
注意:大于length的key会丢弃
let newArr = Array.from(arr); Array.isArray(newArr) // true
-
Array.prototype.slice.call 方法
let newArr = Array.prototype.slice.call(arr); Array.isArray(newArr) // true
-
Array.prototype.map.call 方法
let newArr = Array.prototype.map.call(arr,(i)=>i) Array.isArray(newArr) // true
-
Array.prototype.filter.call 方法
let newArr = Array.prototype.filter.call(arr,(i)=>i) Array.isArray(newArr) // true
依赖 迭代器(iterable) 方法,在类数组实现了迭代器(iterable)的情况下也能使用比如:
// 需要实现 迭代器(iterable) 才行否则报错
Array.isArray([].concat(...arr)) // true
总结:
类数组只是个对象,实现了数组的方法而已,并非真实数组,很多方法不存在,需要开发者自己去实现,常规库一般只实现个forEach方法,并且会添加一些自定义方法。 如果需要用数组的所有方法请将它转化为真数组。
如果需要高性能地操作数据,还是建议将类数组转换为真实数组使用。当然,如果数据量很小,或只需简单操作,使用类数组也不会有太大问题。