🤭拜托,真正的数组是可以变更长度的

235 阅读5分钟

哈哈 会爬树的金鱼,树上的金鱼呦😀

前言

面试官:说一下类数组和数组的区别?

我:类数组和数组一样啊。没区别

面试官:用document.querySelectorAll选择节点后可以用map或filter方法吗?

我:可以啊。forEach的可以用为啥map或filter不行?

面试官: 回答不错,回去等消息吧

我:

640.gif

识别真数组😘

真假美猴王👻

类数组,顾名思义是类似数组的数据结构,它有数组的部分特征但又不是数组 类数组并不是真正的数组,所以不能直接使用数组的方法。可以用 Array.isArray 来判断是否是真数组

常见的类数组有:

  1. Arguments对象:函数的参数列表,有length属性但不是真数组。

    function foo() {
        console.log(arguments.length); // 3
        console.log(Array.isArray(arguments)); // false
    }
    foo(1, 2, 3);
    
  2. HTMLCollection对象:文档的节点列表,有length和可迭代性但不是真数组。

    const elements = document.querySelectorAll('div');
    console.log(elements.length); // 5
    console.log(Array.isArray(elements)); // false
    
  3. 字符串:有length属性和可迭代性但不是真数组。

    console.log('foo'.length); // 3
    console.log(Array.isArray('foo')); // false
    
  4. jQuery对象也不是个真数组

        const $div =  $('div')
        console.log($div.length); // 5
        console.log(Array.isArray($div)); // false
    

实现个类数组😊

从石头里出来的不一定是美猴王,可能是六耳🙈

既然数组本身也是个对象,那么是不是可以用对象来创建个类似数组的结构呢?

一个数组基本要求需要满足两种条件

  1. 索引值 为正整数

  2. 有 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

性能上区别🤦‍♂️

毕竟是山寨货跑不快很正常🦢

类数组毕竟不是真实的数组,方法是我们自己实现的,在大数据面前性能肯定没有原生效率高. 类数组和真实数组有以下几点区别:

  1. 存储方式不同。真实数组使用数字索引存储元素,而类数组使用对象的字符串键来存储,这会占用更多的内存空间。
  2. 连续的内存空间。真实数组的元素存储在连续的内存空间中,这使得查找和操作元素更高效。类数组的元素存储在对象的属性中,没有这种效率优势。
  3. 大小可变性。真实数组的大小是可变的,可以随意添加和删除元素。而类数组的大小通常是固定的,不能直接添加和删除元素,除非实现对应方法(push、pop、slice等)。
  4. 数组方法和属性。真实数组有丰富的数组方法(push、pop、slice等)和数组属性(length)可用,而类数组只有长度length属性,其他方法需要自己去实现。
  5. 迭代效率。由于真实数组元素在内存中是连续的,所以迭代效率更高。而类数组每个元素都需要通过对象属性查找,效率较低。

所以使真实数组在存储和性能方面都优于类数组:

  1. 占用更少内存,更省空间。
  2. 数组操作如查找、插入和删除元素更加高效。
  3. 支持数组全部方法和属性,使用更加灵活方便。
  4. 迭代效率更高,循环遍历更快速。

如果需要高性能地操作数据,还是建议将类数组转换为真实数组使用。当然,如果数据量很小,或只需简单操作,使用类数组也不会有太大问题。

类数组转真实数组🤭

偷梁换柱,假的变成真的,真的还是真的🦉

如何将类数组转真实数组呢?有这些方法:

  1. Array.from():ES6新增方法,可以将类数组对象转为真实数组。

    注意:大于length的key会丢弃

    let newArr = Array.from(arr); 
    Array.isArray(newArr) // true
    
  2. Array.prototype.slice.call 方法

    let newArr = Array.prototype.slice.call(arr); 
    Array.isArray(newArr) // true
    
  3. Array.prototype.map.call 方法

    let newArr = Array.prototype.map.call(arr,(i)=>i)
    Array.isArray(newArr) // true
    
  4. 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方法,并且会添加一些自定义方法。 如果需要用数组的所有方法请将它转化为真数组。

如果需要高性能地操作数据,还是建议将类数组转换为真实数组使用。当然,如果数据量很小,或只需简单操作,使用类数组也不会有太大问题。