JavaScript 知识体系之 Array的 最佳实践

·  阅读 1120
JavaScript 知识体系之 Array的 最佳实践

这是我参与更文挑战的第六天,活动详情查看:更文挑战

上一篇文章,我们总结了数组的基础常用 API,本篇文章我们继续梳理一下数组一些常用的工具方法

数组转字符串

  • join():将一个数组(或一个类数组对象)的所有元素连接成一个字符串并返回这个字符串。如果数组只有一个项目,那么将返回该项目而不使用分隔符。
  • toString():返回一个表示该对象的字符串
  • reduce():对数组中的每个元素执行一个由您提供的 reducer 函数(升序执行),将其结果汇总为单个返回值。
const arr = ["a", "b", "c", "d", "e", "f"];

// 方式一
const str = arr.join(",");
// 方式二
const str1 = arr.toString();
// 方式三
const str2 = arr.reduce(function (accumulator, currentValue) {
  return accumulator + "," + currentValue;
}, "");
复制代码

字符串转数组

  • split():使用指定的分隔符字符串将一个 String 对象分割成子字符串数组,以一个指定的分割字串来决定每个拆分的位置。
const str = "a,b,c,d,e,f ";

const arr = str.split(",");
复制代码

数组排序

sort(), reverse()默认会在每一项上调用 String(),再后再比较字符串的顺序-比较它们的 UTF-16 代码单元值序列(在 Unicode 字符集中的顺序)。

如果想要按照指定的方式排序,则需要传入一个排序函数

实战演练

下面是一个对数组中的数字直接使用sort()排序的例子

const arr = [0, 19, 5, 80, 15, -1, -100];

arr.sort();

const strArr = [...arr].map((item) => {
  // 获取字符串的Unicode码
  const unicodeNum = `${item}`.codePointAt();
  return {
    // 数字本身
    num: item,
    // Unicode码
    unicodeNum,
  };
});

console.log("str", strArr);
// 打印 sort后的结果
console.log("arr", arr);
复制代码

sort() 排序会影响原来的数组,但我们排序后不能影响原来的数据,因此需要新生成一个数组

image.png

我们可以发现 arr.sort() 排序后的结果与 strArrunicodeNum字段升序排列效果一致

来! 分析一波原因

  1. 排序之前 JS 内部将每一个元素通过 String() 转为字符串,返回十进制的字符串表示;

  2. 通过 item.codePointAt(a) 将每一个元素转为 Unicode 码;

    • String.prototype.codePointAt(a) :返回 一个 Unicode 编码点值。

    • 参数 a 是字符串中需要转码的元素的位置(是 一个大于等于  0,小于字符串长度的整数。如果不是一个数值,则默认为  0 )

  3. 升序排列;

下图是一张字符对应的 Unicode 码点表示,以便我们验证字符串排序的准确性!

image.png

  • "-" 负号对应的 Unicode 是 45

image.png

  • "1" 对应的 Unicode 是 49

前文我们也说明了 JS 代码中如何获得 Unicode,即

"-1".codePointAt();
// 45
复制代码
  • codePointAt 没有传递参数,因此参数默认为 0,则取字符串"-1" 的第 0 个位置,也就是 "-" 负号

  • "-" 负号通过转码: "-".codePointAt()  结果就是 45


根据以上的分析,得出结论:

[0, 19, 5, 80, 15, -1, -100].sort()真正进行排序的其实是

["0", "1","5", "8", "1" , "-", "-"].sort()


为了验证我们的结论,来!实践是检验真理的唯一标准!示例代码走起!

const arr2 = [
  { num: 0, tag: "0" },
  { num: 19, tag: "1" },
  { num: 5, tag: "5" },
  { num: 80, tag: "8" },
  { num: 15, tag: "1" },
  { num: -1, tag: "-" },
  { num: -100, tag: "-" },
];
// 取原数组-arr的第一个字符Unicode进行排序
arr2.sort((a, b) => a.tag.codePointAt() - b.tag.codePointAt());

const strArr2 = [...arr2].map((item) => {
  const unicodeNum = `${item.tag}`.codePointAt();
  return {
    num: item.num,

    unicodeNum,
  };
});

console.log("--------------");
console.log("strArr2", strArr2);
console.log("arr2", arr2);
复制代码

image.png

与最前面的 demo 结果可以说一模一样!!!

数字排序

const arr = [0, 19, 5, 80, 15, -1, -100];

// 升序
const datas1 = [...arr].sort((a, b) => a - b);

// 降序
const datas1 = [...arr].sort((a, b) => b - a);
复制代码

对数组中某个 Number 类型的属性进行排序

const list = [
  { age: 100, name: "wang" },
  { age: 12, name: "wang1" },
  { age: 1, name: "wang2" },
  { age: 89, name: "wang3" },
  { age: 2, name: "wang4" },
];

/*
 * @param {String} keyword 对象的某个属性
 * @param {String} sortType 排序类型(降序 'desc',升序 'asce')
 */
const sortArrayObj = (keyword, sortType = "asce") => {
  // 默认升序排列
  if (sortType === "asce") {
    return (item1, item2) => item1[keyword] - item2[keyword];
  }
  if (sortType === "desc") {
    return (item1, item2) => item2[keyword] - item1[keyword];
  }
  return 0;
};

const newDatas1 = [...list].sort(sortArrayObj("age"));

console.log(newDatas1);
复制代码

sort() 排序会影响原来的数组,但我们排序后不能影响原来的数据,因此需要新生成一个数组

英文排序

  • localeCompare():返回一个数字来指示一个参考字符串是否在排序顺序前面或之后或与给定字符串相同。
const datas = ["e", "d", "g", "c", "b", "a"];
const list = [
  { age: 100, name: "e" },
  { age: 12, name: "d" },
  { age: 1, name: "c" },
  { age: 89, name: "b" },
  { age: 2, name: "a" },
];

const res = [...list].sort((a, b) => `${a.name}`.localeCompare(`${b.name}`));
/*
  0: {age: 2, name: "a"}
  1: {age: 89, name: "b"}
  2: {age: 1, name: "c"}
  3: {age: 12, name: "d"}
  4: {age: 100, name: "e"}
*/
console.log(res);
复制代码

当比较大量字符串时, 比如比较大量数组时, 最好创建一个 Intl.Collator 对象并使用 compare (en-US) 属性所提供的函数。

const datas = ["e", "d", "g", "c", "b", "a"];

const res = [...datas].sort(new Intl.Collator("en").compare);
//  ["a", "b", "c", "d", "e", "g"]
console.log(res);
复制代码

中文排序

如果我们希望我们的中文按照首字母拼音排序,该怎么处理?

此时,可以使用中文简体的 BCF 47 语言标记字符串 zh 进行排序,代码如下:

var arrUsername = [
  "陈坤",
  "邓超",
  "杜淳",
  "冯绍峰",
  "韩庚",
  "胡歌",
  "黄晓明",
  "贾乃亮",
  "李晨",
  "李易峰",
  "鹿晗",
  "井柏然",
  "刘烨",
  "陆毅",
  "孙红雷",
];

arrUsername.sort(new Intl.Collator("zh").compare);
// 结果是:["陈坤", "邓超", "杜淳", "冯绍峰", "韩庚", "胡歌", "黄晓明", "贾乃亮", "井柏然", "李晨", "李易峰", "刘烨", "陆毅", "鹿晗", "孙红雷"]
复制代码

Intl API详细可以参考这篇文章JS Intl 对象完整简介及在中文中的应用

求最大值

console.log(Math.max(1, 3, 2));
// expected output: 3

console.log(Math.max(-1, -3, -2));
// expected output: -1

const array1 = [1, 3, 2];

console.log(Math.max(...array1));
// expected output: 3
复制代码

  • reduce(): 对数组中的每个元素执行一个由你提供的函数(升序执行),将其结果汇总为单个返回值。
const list = [{ x: 22 }, { x: 42 }, { x: 42 }];
const data = list.reduce(
  (acc, cur) => {
    return Math.max(acc.x || acc, cur.x || cur);
  },
  { x: -Infinity },
);

// 根据值过滤之对应的数据
const res = list.filter((item) => item.x === data);

/*
    0: {x: 42}
    1: {x: 42}
*/
console.log(res);
复制代码

数字累加减乘除

使用 reduce()实现

const list = [1, 2, 3, 4, 5, 6, 7, 8, 9];
/*
 * @param {Array} arr 数组(每个元素必须是数字)
 * @param {String} type 计算类型
 * 加法 'add',减法 'subtraction'
 * 乘法 'multiplication',除法 'division'
 */
function calculator(arr, type) {
  if (!Array.isArray(arr)) {
    throw new Error("请传入数组");
  }
  const obj = {
    add: (a, b) => a + b,
    subtraction: (a, b) => a - b,
    multiplication: (a, b) => a * b,
    division: (a, b) => a / b,
  };

  if (!obj[type]) {
    throw new Error("请输入正确的运算符");
  }

  const res = arr.reduce((count, current, index, source) => {
    return obj[type](count, current);
  });

  return res;
}

const data = calculator(list, "add");
// 45
console.log(data);
复制代码

计算数组中每个元素出现的次数

使用 reduce()实现

const names = ["Alice", "Bob", "Tiff", "Bruce", "Alice"];

const countedNames = names.reduce(function (allNames, name) {
  if (name in allNames) {
    allNames[name]++;
  } else {
    allNames[name] = 1;
  }
  return allNames;
}, {});

console.log(countedNames);
复制代码

将二维数组转化为一维

使用 reduce()实现

var flattened = [
  [0, 1],
  [2, 3],
  [4, 5],
].reduce(function (a, b) {
  return a.concat(b);
}, []);

console.log("将二维数组转化为一维");
console.log(flattened);
复制代码

使用递归实现

// 手写实现
/*
数组中每个元素都像一个子节点,非数组元素是叶节点。
因此,这个例子中的输入数组是一个高度为 2 有 7 个叶节点的树。打平这个数组本质上是对叶节点的按
序遍历。

第一个参数:源数组
第二个参数:指定打平到第几级嵌套
第三个参数:新数组
*/

function flatten(sourceArray, depth, flattenedArray = []) {
  for (const element of sourceArray) {
    if (Array.isArray(element) && depth > 0) {
      flatten(element, depth - 1, flattenedArray);
    } else {
      flattenedArray.push(element);
    }
  }
  return flattenedArray;
}

const arr = [[0], 1, 2, [3, [4, 5]], 6];

console.log(flatten(arr, 1));

// [0, 1, 2, 3, [4, 5], 6]
复制代码

使用 flat()实现

Array.prototype.flat()用于将嵌套的数组“拉平”,变成一维的数组。该方法返回一个新数组,对原数据没有影响。

const arr1 = [0, 1, 2, [3, 4]];

console.log(arr1.flat());
// expected output: [0, 1, 2, 3, 4]

const arr2 = [0, 1, 2, [[[3, 4]]]];

console.log(arr2.flat(2));
// expected output: [0, 1, 2, [3, 4]]
复制代码

详细内容参考ES 入门-flat

将数组中的深度嵌套对象摊平为一维

const treeData = [
  {
    title: "0",
    value: "0",
    children: [
      {
        title: "0-0",
        value: "0-0",
        parentId: "0",
        children: [
          {
            title: "0-0-1",
            value: "0-0-1",
            parentId: "0-0",
          },
        ],
      },
      {
        title: "0-1",
        value: "0-1",
        parentId: "0",
        children: [
          {
            title: "0-1-1",
            value: "0-1-1",
            parentId: "0-1",
            children: [
              {
                title: "0-1-1-1",
                value: "0-1-1-1",
                parentId: "0-1-1",
              },
            ],
          },
        ],
      },
    ],
  },
];

// 树形数据转化为一维数组
const flatData = (datas, res = []) => {
  if (Array.isArray(datas) && datas.length > 0) {
    datas.forEach((item) => {
      const { children, ...rest } = item;
      res.push(rest);
      flatData(children, res);
    });
  }
  return res;
};

const res = flatData(treeData, []);
console.log(res);
复制代码

treeDataToArray

将特定的一维数组结构转化为树形数据

const list = [
  { title: "0", value: "0" },
  { title: "0-0", value: "0-0", parentId: "0" },
  { title: "0-0-1", value: "0-0-1", parentId: "0-0" },
  { title: "0-1", value: "0-1", parentId: "0" },
  { title: "0-1-1", value: "0-1-1", parentId: "0-1" },
  { title: "0-1-1-1", value: "0-1-1-1", parentId: "0-1-1" },
];

// 特定一维数组结构转化为树形数据

const arrToTreeData = (datas, root = []) => {
  const generatorData = (allDatas, childDatas) => {
    const newparentDatas = childDatas.map((childItem) => {
      // 子节点数据
      const nextChildDatas = allDatas.filter(
        (aallItem) => aallItem.parentId === childItem.value,
      );

      if (Array.isArray(nextChildDatas) && nextChildDatas.length > 0) {
        // 设置父节点的 children
        childItem.children = nextChildDatas;

        generatorData(allDatas, nextChildDatas);
      }
      return childItem;
    });

    return newparentDatas;
  };

  if (Array.isArray(datas) && datas.length > 0) {
    // 没有父节点的数据就是根节点
    root = datas.filter((item) => !item.parentId);
    root[0].children = [];

    // 找到父节点为根节点的数据
    const childDatas = list.filter((item) => item.parentId === root[0].value);
    if (Array.isArray(childDatas) && childDatas.length > 0) {
      const newChild = generatorData(list, childDatas);
      // 设置根节点的 children
      root[0].children = newChild;
    }
  }
  return root;
};

const res = arrToTreeData(list, []);
// 打印出 treeData数据
console.log(res);
复制代码

数组取交集

const a = [0, 1, 2, 3, 4, 5];
const b = [3, 4, 5, 6, 7, 8];
const duplicatedValues = [...new Set(a)].filter((item) => b.includes(item));
duplicatedValues; // [3, 4, 5]
复制代码

数组取差集(合并数组并去重)

const a = [0, 1, 2, 3, 4, 5];
const b = [3, 4, 5, 6, 7, 8];
const diffValues = [...new Set([...a, ...b])].filter(
  (item) => !b.includes(item) || !a.includes(item),
);
// [0, 1, 2, 6, 7, 8]
复制代码

检测数组所有元素是否都符合判断条件

const arr = [1, 2, 3, 4, 5];
const isAllNum = arr.every((item) => typeof item === "number");
复制代码

检测数组任意元素是否符合判断条件

const arr = [1, 2, 3, 4, 5]
const hasNum = arr.some(item => typeof item === 'number')
复制代码

查找数组的最大值和最小值

const arr = [1, 2, 3];
Math.max(…arr); // 3
Math.min(…arr); // 1
复制代码

统计数组中相同项的个数

很多时候,你希望统计数组中重复出现项的个数然后用一个对象表示。那么你可以使用 reduce 方法处理这个数组。

下面的代码将统计每一种车的数目然后把总数用一个对象表示。

var cars = ["BMW", "Benz", "Benz", "Tesla", "BMW", "Toyota"];
var carsObj = cars.reduce(function (obj, name) {
  obj[name] = obj[name] ? ++obj[name] : 1;
  return obj;
}, {});
carsObj; // => { BMW: 2, Benz: 2, Tesla: 1, Toyota: 1 }
复制代码

再举个例子

假设现在有一个数列,你希望更新它的每一项(map 的功能)然后筛选出一部分(filter 的功能)。如果是先使用 map 然后 filter 的话,你需要遍历这个数组两次。

在下面的代码中,我们将数列中的值翻倍,然后挑选出那些大于 50 的数。有注意到我们是如何非常高效地使用 reduce 来同时完成 mapfilter 方法的吗?

const numbers = [10, 20, 30, 40];
const doubledOver50 = numbers.reduce((finalList, num) => {
  num = num * 2;
  if (num > 50) {
    finalList.push(num);
  }
  return finalList;
}, []);
doubledOver50; // [60, 80]
复制代码

使用解构来交换参数数值

let param1 = 1;
let param2 = 2;
[param1, param2] = [param2, param1];
console.log(param1); // 2
console.log(param2); // 1
复制代码

使用 reduce 判断圆括号是否匹配

reduce 的另外一个用途是能够匹配给定字符串中的圆括号。对于一个含有圆括号的字符串,我们需要知道 (和 )的数量是否一致,并且 (是否出现在 )之前。

下面的代码中我们使用 reduce 可以轻松地解决这个问题。我们只需要先声明一个 counter 变量,初值为 0。在遇到 (时 counter 加一,遇到 )时 counter 减一。如果左右括号数目匹配,那最终结果为 0。

//Returns 0 if balanced.
const isParensBalanced = (str) => {
  return str.split("").reduce((counter, char) => {
    if (counter < 0) {
      //matched ")" before "("
      return counter;
    } else if (char === "(") {
      return ++counter;
    } else if (char === ")") {
      return --counter;
    } else {
      //matched some other char
      return counter;
    }
  }, 0); //<-- starting value of the counter
};
isParensBalanced("(())"); // 0 <-- balanced
isParensBalanced("(asdfds)"); //0 <-- balanced
isParensBalanced("(()"); // 1 <-- not balanced
isParensBalanced(")("); // -1 <-- not balanced
复制代码

实现栈和队列

栈: 比较贴切的例子就是码砖,叠盘子等,放在上面的先用,放在下面的后用, 即先进后出

队列: 比较贴切的例子就是排队购物,排在前面的人先买到东西,即先进先出

栈的粗略实现

const stack = [];
// 入栈
stack.push("a");
//出栈
stack.pop();
复制代码

队列的粗略实现

const quen = [];
// 入队
quen.push("a");
//出队
stack.unshfit();
复制代码

排序算法-todo

关于几种排序算法可以参考这篇文章🍕JavaScript 知识体系之数组排序算法

深浅复制数组-todo

JavaScript 的深浅复制参考这篇文章

数组去重-todo

可以参考JavaScript 知识体系之数组去重

相关算法思考题

  1. 请⽤将数组[[1,2,4],[1,2,3],[1,3,4],[1,3,2]]快速排序成[[1,2,3],[1,2,4],[1,3,2],[1,3,4]],怎么解

最后

以上就是本篇文章的主要内容,文章浅陋,欢迎各位看官评论区留下的你的见解!

觉得有收获的同学欢迎点赞,关注一波!

20210601205044

往期文章

分类:
前端
标签: