【知识梳理】JS 数组 API 再记

1,637 阅读4分钟

1. 起因

上篇文章提到,由于业务上使用频率的原因,导致自己对数组的某些 api 不甚了解,在我搞清楚了 slicesplicereduce 后,信心满满,自以为已经数组大成了,当时的聊天记录足以证明我的信心。

image.png

但是,最近有几个数组的使用场景,结合看了一些大佬总结数组的文章,让我了解到,对一些方法的理解还有不足,需要继续巩固一下。

2. 场景

2.1. sort 的参数与规则

[1, 3, 2].sort((a, b) => a - b);
[1, 3, 2].sort((a, b) => b - a);

a - bb - a 哪个是从小到大?

MDN 上的定义来看

  • 如果 a - b < 0ab 的前面
  • 如果 a - b > 0ba 的前面
  • 如果 a - b == 0ab 可以认为不会交换

在加上 console 看看 a , b 到底是什么?

image.png

a - b 为例

可以看到 a , b 在第一次比较时分别是 3, 1 (b 是第一个元素,a 是第二个元素)

3 - 1 > 0 及 a - b > 0 , 所以 b 应该在 a 的前面

所以 1 应该在 3 的前面, 可以看出是从小到大

之前我 想当然 的以为, a 应当是第一个元素 1, b 是第二个元素 3 , a - b < 0 , 也能推出是从小到大,但是这样一来,对参数的理解就与 chrome 浏览器实现不一致了。

PS: 感谢 @shyg 的指正,以上仅代表了 chrome 浏览器,部分浏览器参数的逻辑为下表,有兴趣的小伙伴可以自己去试试相应的浏览器~

浏览器参数顺序
chromeb 是前面的元素,a 是后面的元素
firefoxa 是前面的元素,b 是后面的元素
safari同 chrome
Microsoft Edge同 chrome

sort 方法还有一个注意点是虽然函数的返回值是一个排列好的数据。但是原数组已经发生了改变,在实际使用中一定要注意,避免出现以下情况!

var arr = [1, 2, 3];
var arr1 = arr.sort((a, b) => b - a);

// 然后以为 arr 还是原数组的情况 ~

2.2. 在 for 循环或 forEachreturn

for 循环中 return 确实可以跳出循环并返回结果

forEach 却并没有这个功效

var array = [1, 2, 3, 4, 5];

// 假设需要实现一个 get 方法,获取数组中第一个大于 3 的数字
function get(arr) {
    // 可以这么写 ✅
    for (let i of arr) {
        if (i >= 3) {
            return i;
        }
    }
    return null;
    
    // 错误 ❌
    arr.forEach((item) => {
        if (item >= 3) {
            return item;
        }
    });
    return null;
    
    // 错误 ❌
    const result = arr.forEach((item) => {
        if (item >= 3) {
            return item;
        }
        
        return null;
    });
    return result;
}

2.3. 更准确的 includes

这个是看一篇高赞数组总结文章学习到的

includes 可以判断数组中是否包含 NAN,但是 indexOf 不行

2.4. 容易遗忘的 unshift

业务中从头部插入一个数组有挺多的场景,昨天在业务项目中 review 了一下

发现写法挺多的

[1].concat([2, 3]);

[1, ...[2,3]]

如果可以直接改变原数组,直接用 [2, 3].unshift(1) 也是个不错的选择

2.5. reduce 的孪生兄弟

api 时发现有一个 reduceRight 是从后往前执行 reduce 的可以当成 [].reverse().reduce 使用。

在观察 reduceRight 方法时,突然对 reduce/reduceRight 第三个参数有些好奇。想知道到底代表的是哪一位的下标。

image.png

经过实践发现,第三个参数代表的是第二个参数在数组中的下标,且与从前到后、或从后到前的遍历顺序无关

比如

  • 上面的 reduceRight 首次遍历,当前项是 5 ,所以输出的下标是 4
  • 下面没有加默认值的 reduceRight 首次遍历,当前项是 4, 所以输出的下标是 3

2.6. Mock数据高手 fill

Array(5).fill(1); // [1, 1, 1, 1, 1]

如果我们需要制造一个比较长的对象数组,用于页面滚动相关测试时,使用 fill 会让我们不用频繁的复制粘贴

这个收录进来的原因:是之前没认真想过这个方法的执行原理,有时候着急会写出 [].fill(5) 这样的 错误 写法,认为是在数组里填充 5 个元素~

2.7. for of 的使用场景

如果我们不需要使用到循环的下标,使用 for of 可以让代码看上去更简洁,有以下场景可以使用

// 第一种情况,找到数组中符合要求的一项
let arr = [];
let findItem = defaultValue;

// 正确 ✅
for (let item of arr) {
    if (item === xxx) {
        findItem = item;
        break;
    }
}

// 使用find也是可以的 ✅
const findItem = arr.find((item) => item === xxx) || defaultValue;


// 第二种情况,遍历数组,用里面的数据配合原生api组合调用
let arr = [];

// 例如使用数组元素,绘制canvas的场景
for (let item of arr) {
    document.getElementById('canvas').drawImage(item.url, item.x, item.y, item.width, item.height)
}

2.8. everysome

every 需要数组中所有的项满足条件,才可返回 true

some 需要数组中只有一项满足条件,即可返回 true

// 他们均可以让以下场景的语义化更简洁

const arr = [100, 100, 100];
const arr1 = [100, 0, 0];

// for 循环写法
let isAllRight = true;
for (let item of arr) {
    if (item !== 100) {
        isAllRight = false;
        break;
    }
}

let hasAllRight = false;
for (let item of arr) {
    if (item === 100) {
        hasAllRight = true;
        break;
    }
}

// ✅ every 写法
const isAllRight = arr.every((item) => item === 100);

// ✅ some 写法
const hasAllRight = arr1.some((item) => item === 100);

// 如果数组是如上的简单结构,其实 some 可以用 includes/indexOf 代替
const hasAllRight = arr1.includes(100);
const hasAllRight = arr1.indexOf(100) > -1;

// 但是如果数组是 [{score: 100}, {score: 100}, {score: 0}] 对象格式,就需要使用 some