# 深入 JavaScript 数组相关方法 和 API

本章节

我们来学习数组相关的方法,操作数组的方法非常多,我们先学习 ES5 中的一些方法,等学习到 ES6 之后,再学习 ES6 中新增的相关方法。

数组的操作非常重要,我们后期的项目开发或面试中,对于数组的操作那是家常便饭,所以一定要掌握

相关方法也可参考 MDN 文档同步学习 (opens new window)

# 一、数组的头尾操作

首先我们来学习,如何在数组的头部或尾部添加元素或删除元素的操作

实例方法 描述 是否更改原数组
push() 将一个或多个元素添加到数组的末尾,并返回该数组的新长度
pop() 从数组中删除最后一个元素,并返回被删除元素的值
unshift() 将一个或多个元素添加到数组的头部,并返回该数组的新长度
shift() 从数组中删除第一个元素,并返回该元素的值

# 1、push()方法

TIP

  • 向数组的末尾添加一个多个元素,每个元素之间用,逗号隔开
  • 并返回数组的新长度
var arr = [1, 2, 3];

// 向数组的尾部添加元素4
arr.push(4);
// 向数组的尾部添加5,'我' ,同时用变量len接受数据的新长度
var len = arr.push(5, "我");

console.log(arr);
console.log("数组的长度:" + len); // 数组的长度:6

push 方法根据 length 属性来决定从哪里开始插入给定的值

# 2、pop() 方法

TIP

  • 从数组中删除最后一个元素,并返回该元素(被删除元素)的值
  • 当数组为空时,返回值为undefined
var arr = [12, 23, 56, 11];
var item = arr.pop();

console.log(arr); // 输出删除最后一项后的值 [12, 23, 56]
console.log(item); // 返回被删除元素的值 11

pop 方法根据 length 属性来确定最后一个元素的位置

# 3、unshift() 方法

TIP

  • 一个或多个元素添加到数组的开头,每个元素之间用,逗号隔开
  • 并返回该数组的新长度
var arr = [12, 23, 56, 11];
arr.unshift(66);
var len = arr.unshift(0);
console.log(len); // 6
console.log(arr); // [0, 66, 12, 23, 56, 11]

# 4、shift() 方法

TIP

  • 从数组中删除第一个元素,并返回该元素的值
  • 当数组为空时,返回值为undefined
var arr = [12, 23, 56, 11];
var item = arr.shift(); // 删除数组中下标为0的项

console.log(item); // 返回被删除的项 12
console.log(arr); // [23, 56, 11]

# 5、案例:筛选数组中所有大于等于 10 的元素

筛选数组中所有大于等于 10 的元素

var arr = [1, 3, 68, 33, 5, 9, 34, 55];
function filter(arr) {
  var arr2 = [];
  for (var i = 0; i <= arr.length; i++) {
    if (arr[i] >= 10) {
      arr2.push(arr[i]);
    }
  }
  return arr2;
}
console.log(filter(arr));

# 二、数组中元素的(增、删、改、查)

如果我们要对数组中非头尾的元素,做相关的增、删、改、查操作,就需要用到下面的这些方法

实例方法 描述 是否更改原数组
slice() (查询):提取原数组的一部分并返回一个新数组
splice() (新增):在指定位置插入一项或多项新元素
(删除):在指定位置删除指定项
(更新):在指定位置删除指定项,同时在此处添加一项或多项

# 1、slice()方法

TIP

  • 查询原数组中的一部分元素,然后将查询到的元素复制到一个新数组,并将新数组返回
  • slice() 方法不会更改原数组
  • slice() 中返回的元素是从原数组中浅拷贝过来的。

浅拷贝

  • 数组中的值如果是基本类型,直接把值拷贝一份过来
  • 如果是引用类型,拷贝的是引用类型的地址,当修改 slice()返回数组中的引用类型值时,原数组也会变

语法:

slice(start, end);
  • start 表示查找的开始索引,end 表示查找的结束索引
  • start 和 end 可正可负,正数表示从左往右数,下标从 0 开始,负数表示从右往左数,下标从-1 开始
  • 查找结果包含 start 部分,不包含 end 部分
  • 如果只有一个参数,如 slice(2),表示从下标 2 开始,提取后续所有项元素
  • 如果参数只一个,并且是负数,如:slice(-2) 表示提取数组最后 2 个元素

# 1.1、两个参数都为正

TIP

两个参数都为正,如 slice(2,3) 表示从下标 2 开始,提到到下标 3 中间所有元素,不包括下标 3 的元素

var arr = [1, 2, 3, 4, 5, 6];
var arr2 = arr.slice(1, 4); // 从原数组下标 1查找到下标4,将查找到元素作为一新数组返回

console.log(arr); // [1, 2, 3, 4, 5, 6]  并不会改变原数组
console.log(arr2); // [2, 3, 4]

# 1.2、只有一个参数,参数为正

TIP

如果只有一个参数,如 slice(2)表示从下标 2 开始,提取后续所有项元素

var arr = [1, 2, 3, 4, 5, 6];
var arr1 = arr.slice(2); // 从下标2开始,提取后续所有项元素

console.log(arr1); // [3, 4, 5, 6]

# 1.3、只有一个参数,参数为负

TIP

如果参数只一个,并且是负数,如:slice(-2) 表示提取数组最后 2 个元素

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

var arr1 = arr.slice(-2); // 提取最后2个元素
console.log(arr1); // [5, 6]

# 1.4、两个参数都为负数

TIP

  • 如果两个参数都为负数,如:slice(-3,-2)表示提取原数组中的倒数第 3 个元素到倒数第二个元素(不包含第二个元素)
  • slice(-2,0) 查询结果为空数组
var arr = [1, 2, 3, 4, 5, 6];
var arr1 = arr.slice(-3, -2); // 提取倒数第3个元素到倒数第2个元素,不包含倒数第二个元素
var arr2 = arr.slice(-3, 0);

console.log(arr1); // [4]
console.log(arr2); // []

# 1.5、slice 提取原数组中数据,是以浅拷贝方式复制过来

var arr = [1, ["A", "B"], 2, 3];
var arr2 = arr.slice(1, 3);
arr2[0][1] = "更"; // 更改arr2中第1项的内容

// 更改arr2中的内容,原数组arr中内容也发生了改变
console.log(arr); // [1, ["A", "更"], 2, 3]
console.log(arr2); // [["A", "更"],2]

记忆准则 slice(start,end) :

  • start 和 end 表示查找的起始与结束索引,其值可正可负,正索引是从左往右数,下标从 0 开始,负索引是从右往左数,下标从-1 开始
  • 元素查找方向,都是从起始索引往右查找
  • 查找的元素,只包含第一个参数索引值,不包含第二个参数索引
  • 如果只有一个参数,表示起始位置,从这个位置提取往后所有元素。
  • start 的默认值可以理解为 0,end 的默认值可以理解为数组长度-1
  • arr.slice() 相当于把整个数组浅拷贝一份

# 2、splice()方法

TIP

  • splice()方法,可以实现对数组中元素的 删除添加更新(删除并替换)操作
  • 被删除的元素组成一个数组,被返回,如果没有删除元素,则返回空数组

语法

splice(index, deleteCount, item1, item2, itemN);

index 表示修改的开始位置(下标)

  • 如果超过数组长度,表示从数组尾部开始添加内容
  • 如果是负值,表示数组尾部开始的第几位(必部第一位表示-1)

deleteCount 表示要移除的数组中元素的个数

如果 deleteCount 是 0 或负数,表示不移除元素

  • item1,item2...可选,表示要添加进数组的元素,从 index 位置开始

# 2.1、添加元素

var arr = [1, 2, 3, 4, 5];
// 从下标1位置前面插入两个数
var arr1 = arr.splice(1, 0, "A", "B");
console.log(arr); // [1, 'A', 'B', 2, 3, 4, 5]
console.log(arr1); // []

# 2.2、更新(删除并替换)元素

var arr = [1, 2, 3, 4, 5];
// 从下标1的位置,删除2个元素(包括下标1的元素),然后再加两个元素
var arr1 = arr.splice(1, 2, "A", "B");
console.log(arr); // [1, 'A', 'B', 4, 5]
console.log(arr1); // [2, 3]

# 2.3、删除元素

var arr = [1, 2, 3, 4, 5];
var arr1 = arr.splice(1, 2);
console.log(arr); // [1, 4, 5]
console.log(arr1); // [2, 3]

var arr = [1, 2, 3, 4, 5];
var arr1 = arr.splice(2); // 删除索引2后所有元素,包括索引2位置上元素
console.log(arr); // [1, 2]
console.log(arr1); // [3, 4, 5]

# 2.4、index 下标为负数时

  • index 为负数,表示从后往前找元素的位置,从后往前,元素下标从-1 开始
var arr = [1, 2, 3, 4, 5];
// 从倒数第三个元素开始,删除 2个元素(包括倒数第三个)
var arr1 = arr.splice(-3, 2);
console.log(arr); // [1, 2, 5]
console.log(arr1); // [3, 4]

记忆准则:

  • index 表索引,索引可正可负,正表示从左往右数,下标从 0 开始,负表示从右往左数,下标从-1 开始
  • deleteCount 表示删除长度,0 和负数不删除,如果不写,表示 index 处删除往后所有元素(包括 index 处元素)
  • splice 删除元素,包含 index 索引位置元素

# 三、数组与字符串相互转换

实例方法 描述 是否更改原数组
join() 将一个数组的所有元素以特定的某种字符连接成一个字符串
并返回这个字符串
不会
toString() 将数组转换为字符串,然返回该 字符串 不会

以下方法为字符串的实例方法

实例方法 描述
split() 根据指定的分隔符将一个字符串分割成一个字符串数组
charAt() 从一个字符串中返回指定的字符 str.charAt(index)

# 1、join()方法

TIP

  • 将一个数组的所有元素以特定的某种字符连接成一个字符串,并返回这个字符串
  • 如果不指定连接字符,则默认以逗号来分隔连接
  • 这个方法不会改变原数组,会生成一个新的字符串
var arr = ["a", "b", "c", "d"];
var str = arr.join();
console.log(str); // a,b,c,d
console.log(arr); // ['a','b','c','d']

var str2 = arr.join("");
console.log(str2); // abcd
var str3 = arr.join("**");
console.log(str3); // abcd a**b**c**d

# 2、toString()方法

  • 将数组转成以,逗号分隔的字符串,同时返回该字符串
var arr = [1, 2, 3];
var str = arr.toString();
console.log(str);
  • toString 方法在内部调用 join()方法拼接数组中的元素并返回一个字符串,其中包含用逗号分隔的每个数组元素
var arr = [1, 2, 3, 4];
var str = arr.toString();
console.log(str);

// 修改原型上join方法
Array.prototype.join = function () {
  var str = "";
  for (var i = 0; i < this.length; i++) {
    str += this[i] + "*";
  }
  return str;
};
// 也可以只修改当前实例的join方法 arr.join=function(){  }
console.log(arr.toString());

# 3、split()方法 字符串操作方法

TIP

  • 数组的join()方法可以使数组转为字符串
  • 字符串的split()方法可以使字符串转为数组

image-20211220235249527

  • split()可以使字符串以某种分隔符进行分隔,分隔后的字符串组成一个数组返回
  • 分隔符为空字符串,则将原字符串中每个字符的数组形式返回
var str = "a*b*c*d";
var arr1 = str.split("");
var arr2 = str.split("*");
console.log(arr1); // ['a', '*', 'b', '*', 'c', '*', 'd']
console.log(arr2); // ['a', 'b', 'c', 'd']

"a-b-c-d-e-f-g-h".split(""); // ['a', '-', 'b', '-', 'c', '-', 'd', '-', 'e', '-', 'f', '-', 'g', '-', 'h']
"a-b-c-d-e-f-g-h".split("-"); // ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']
  • 回顾之前讲的rgb(2,33,55) 颜色的分隔
var rgb = "rgb(2,33,55)";
// \d 表示 0-9之间的任意数字 [^\d] 排除0-9之间的任意数字  +表示前面的字符可以出现任意次
// 只要不是[0-9]之间的单个或多个数字,其它字符都可以做为分隔符来分隔字符串
var rgbArr = rgb.split(/[^\d]+/);
console.log(rgbArr);

# 4、实战应用:获取 url 地址中的参数

var url = "https://www.icodingedu.com/goods/show/42?targetId=71&preview=0";
var obj = {};
var str = url.split("?")[1].split("&");
console.log(str); //  ['targetId=71', 'preview=0']
for (let i = 0; i < str.length; i++) {
  var param = str[i].split("=");
  obj[param[0]] = param[1];
}
console.log(obj); // {targetId: '71', preview: '0'}

# 5、charAt()方法 字符串操作方法

TIP

  • charAt() 方法从一个字符串中返回指定的字符
str.charAt(index);
  • 一个介于 0 和字符串长度减 1 之间的整数
  • 如果没有提供索引,charAt() 将使用 0
  • 字符串中的字符从左向右索引,第一个字符的索引值为 0,最后一个字符(假设该字符位于字符串 str 中)的索引值为 str.length - 1
var str = "我爱你!";
console.log(str.charAt(0)); // 我
console.log(str.charAt(1)); // 爱
console.log(str.charAt(2)); // 你

# 6、字符串与数组相似点

TIP

  • 字符串也可以像数组一样,使用下标进行访问
  • 也可以用 charAt()方法来访问,charAt()
var str = "我爱你!";
console.log(str[0]); // 我
console.log(str[1]); // 我
console.log(str[2]);
  • 字符串可以像数组一样,利用 for 循环来遍历
var str = "我爱你!";
for (var i = 0; i < str.length; i++) {
  console.log(str[i]);
}

# 四、判断数组中元素方法

实例方法 描述 是否更改原数组
indexOf() 返回在数组中可以找到给定元素的第一个索引,如果不存在,则返回 -1。
lastIndexOf() 返回指定元素在数组中的最后一个的索引,如果不存在则返回 -1
从指定位置开始逆向查找(即从后往前找)
includes() 法用来判断一个数组是否包含一个指定的值,
根据情况,如果包含则返回 true,否则返回 false

# 1、indexOf() 方法

TIP

  • 返回在数组中可以找到给定元素的第一个索引,如果不存在,则返回 -1。
  • 查找时,使用的是(全等运算===)

语法

indexOf(searchElement, fromIndex);
  • searchElement 要查找的元素
  • fromIndex 开始查找的位置(索引)
    • 如果 fromIndex的值 >= 数组长度,意味着不会在数组中查找,返回-1
    • 如果 fromIndex 是一个负数,如 indexOf('a',-2); 表示从倒数第二个元素开始查找,向后查找到数组尾部
["A", "B", "C", "D"]
  .indexOf("D") // 3 返回D的下标为3
  [("A", "B", "C", "D")].indexOf("F") // -1 F不存在返回-1
  [("A", "B", "C", "D", "B", "B")].indexOf("B"); // 1 B存在多次,则返回第一次出现位置的下标

var arr = [2, 9, 1, 1, 5];
arr.indexOf(2); // 0
arr.indexOf(1, 3); // 3
arr.indexOf(1, -3); // 2
arr.indexOf(1, -1); // -1

# 2、lastIndexOf()

TIP

  • 返回指定元素在数组中的最后一个的索引,如果不存在则返回 -1
  • 内容判断使用的 全等
  • 从指定位置开始逆向查找(即从后往前找)

语法

lastIndexOf(searchElement);
lastIndexOf(searchElement, fromIndex);
  • searchElement 被查找的元素
  • fromIndex 查找的的开始下标,默认值为 arr.length-1,(即从后向前查找整个数组)
  • 如果 fromIndex 不写、或大于等于数组长度,则整个数组会被查找
  • 如果 fromIndex 为负值,从指定位置向前查找
  • 如果 fromIndex 负值的绝对值大于数组长度,则数组不会被查找,返回-1
var arr = [1, 3, 4, 5, 3, 8, 1];
var _index = arr.lastIndexOf(3);
console.log(_index); // 4

var _index = arr.lastIndexOf(1);
console.log(_index); // 6
var arr = [1, 3, 4, 5, 3, 8, 1];
var _index = arr.lastIndexOf(1, -1);
console.log(_index); // 6

var _index = arr.lastIndexOf(1, -2);
console.log(_index); // 0

var _index = arr.lastIndexOf(1, -8);
console.log(_index); // -1

# 3、includes() 方法

TIP

  • 法用来判断一个数组是否包含一个指定的值
  • 根据情况,如果包含则返回 true,否则返回 false
  • 查找时,使用的是(全等运算===)

语法

includes(searchElement, fromIndex);
  • searchElement 需要查找的元素
  • fromIndex 表示查找的开始索引。
    • 如果fromIndex超过 >= 数组长度,直接返回 false
    • 如果 fromIndex 为负数,从负的索引处一直查找到数组的尾部
[11, 22, 33]
  .includes("22") // false
  [(1, 2, 3, 4)].includes(3); // true
[1, 2, 3, 4].includes(2, -2); // false
[1, 2, 3, 4].includes(4, -2); // true

# 4、应用案例:数组去重

function unique(arr) {
  // 如果传入的类型不是数组,啥也不做
  if (!Array.isArray(arr)) {
    return;
  }
  var result = [];
  for (var i = 0; i < arr.length; i++) {
    // if(!~result.indexOf(arr[i]))
    if (!result.includes(arr[i])) {
      result.push(arr[i]);
    }
  }
  return result;
}
var arr = [
  1,
  1,
  2,
  2,
  "true",
  "true",
  true,
  true,
  undefined,
  undefined,
  null,
  null,
  [],
  [],
  NaN,
  NaN,
  "NaN",
  {},
  {},
  function () {},
  function () {},
];
console.log(unique(arr));
//这种方式,是没有办法过滤 引用类型的,但针对基本数据类型没有问题

# 5、案例:查找元素在数组中所有索引(下标)

TIP

  • 利用 indexOf 方法,返回查找到的元素索引
  • 如果返回索引不为-1,把索引添加到新数组,
  • 则继续从当前索引的下一个索引位置开始查找,找到就返回索引继续查找,找不到则停止查找。
/**
 * 找到数组中指定元素的所有索引(下标)
 * @param arr 查找的数组
 * @param element 查找的元素
 */
function findIndexs(arr, element) {
  var indexArr = [];
  var _index = arr.indexOf(element);
  // 返回索引不是-1,则继续查找 _index!=-1
  while (~_index) {
    indexArr.push(_index);
    _index = arr.indexOf(element, _index + 1);
  }
  return indexArr;
}
var arr = ["a", "b", "c", "d", "e", "b", "b", "f", "b"];
console.log(findIndexs(arr, "b"));
  • 递归方法
function findIndexs(arr, el) {
    var arrIndex = []; // 返回字符下标数组
    var index = 0; // 每次查找索引
    // 立即执行函数
    (function findIndex(arr, el) {
        index = arr.indexOf(el, index);
        if (index !== -1) {
            arrIndex.push(index);
            findIndex(arr, el, ++index;);
        }
    })(arr, el);
    // findIndex(arr, el);
    return arrIndex;
}

var arr = ["a", "b", "a", "c", "c", "b", "c", "a", "a", "d"];
console.log(findIndexs(arr, "a"));

# 五、数组拼接与反转

实例方法 描述 是否更改原数组
concat() 方法用于合并两个或多个数组。
此方法不会更改现有数组,而是返回一个新数组
reverse() 方法将数组中元素的位置颠倒,并返回该数组
该方法会改变原数组。

# 1、concat()方法

TIP

  • 法用于合并两个或多个数组
  • 此方法不会更改现有数组,而是返回一个新数组
  • concat 返回的新数组是元素的浅拷贝
var arr1 = [1, 2, 3, 4];
var arr2 = [5, 6, 7, 8];
var arr3 = [9, 10, 11, 12];

var arr = arr1.concat(arr2, arr3);
console.log(arr); // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]

console.log(arr1); // [1, 2, 3, 4]
console.log(arr2); // [5, 6, 7, 8]
console.log(arr3); // [9, 10, 11, 12]
var arr1 = [1, 2, [4, 5]];
var arr2 = ["a", "b", "c"];
var arr3 = arr1.concat(arr2);
console.log(arr3); // [1, 2,  ['改', 5], 'a', 'b', 'c']
arr3[2][0] = "改";
console.log(arr1); // [1, 2, ['改', 5]]
console.log(arr3); // [1, 2,  ['改', 5], 'a', 'b', 'c']

# 2、reverse()方法

TIP

  • reverse()方法可以将一个数组中的全部项顺序反过来显示。
  • 他会改变原组。
var arr = [1, 2, 3, 4, 5];
arr.reverse();
console.log(arr); // [5,4,3,2,1]
  • 将字符串进行倒序显示
var str = "清晨我上马";
var arr = str.split("");
arr.reverse();
var str2 = arr.join("");
console.log(str2); // 马上我晨清

# 六、筛选数组元素

方法 描述 是否更改原数组
filter() 返回所有通过函数测试成功的元素
其返回值为测试成功的元素组成的新数组
不会
find() 返回数组中满足提供的测试函数的第一个元素的值
否则返回 undefined (opens new window)
不会

# 1、filter()方法

返回所有通过函数测试成功的元素,其返回值为测试成功的元素组成的新数组

语法:

// 回调函数
filter(callbackFn);
filter(callbackFn, thisArg);

// 内联回调函数
filter(function (element) {
  /* … */
});
filter(function (element, index) {
  /* … */
});
filter(function (element, index, array) {
  /* … */
});
filter(function (element, index, array) {
  /* … */
}, thisArg);

详细解读

  • callbackFn 用来测试数组中每个元素的函数,返回 true 表示该元素通过测试,保留该元素,false 则不保留

  • callbackFn 函数,接受以下三个参数

    • element 数组中当前正在处理的元素
    • index 正在处理的元素在数组中的索引
    • array 调用了 filter()的数组本身
  • thisArg 可选,执行 callbackFn 时,用于 this 的值

# 2、找出数组中所有小于 10 的元素

// 筛选出所有小于10的元素
var arr = [1, 22, 5, 12, 45, 9, 10, 33, 8];
var arr2 = arr.filter(function (value) {
  return value < 10;
});
console.log(arr2); //  [1, 5, 9, 8]

// 返回字符串长度>=6的所有元素
var arr = ["iabc", "iconaff", "bef", "afei", "aiconf"];
var arr2 = arr.filter(function (value) {
  return value.length >= 6;
});
console.log(arr2); //  ['iconaff', 'aiconf']

# 3、自已封装简单版 filter 方法

// 大于等于10
var arr = [1, 3, 68, 33, 5, 9, 34, 55];
function filter(arr) {
  var arr2 = [];
  for (var i = 0; i <= arr.length; i++) {
    if (arr[i] >= 10) {
      arr2.push(arr[i]);
    }
  }
  return arr2;
}
console.log(filter(arr));

/**
 * 找到数组中所有满足测试函数条件的元素
 * @param arr 查找的数组
 * @param fn 测试函数
 */
function filter(arr, fn) {
  var arr2 = [];
  for (var i = 0; i <= arr.length; i++) {
    // 为真,就添加
    if (fn(arr[i])) {
      arr2.push(arr[i]);
    }
  }
  return arr2;
}
// 定义数组
var arr = [1, 3, 68, 33, 5, 9, 34, 55];
// 筛选出大于等于10的元素
var arr2 = filter(arr, function (value) {
  return value >= 10;
});
console.log(arr2);
// 筛选出小于等于10的元素
var arr3 = filter(arr, function (value) {
  return value < 10;
});
console.log(arr3);

# 4、find() 方法

返回数组中满足提供的测试函数的第一个元素的值。否则返回 undefined

// 回调函数
find(callbackFn);
find(callbackFn, thisArg);
// 内联回调函数
find(function (element) {
  /* … */
});
find(function (element, index) {
  /* … */
});
find(function (element, index, array) {
  /* … */
});
find(function (element, index, array) {
  /* … */
}, thisArg);

详细解读

  • callbackFn 用来测试数组中每个元素的函数

  • callbackFn 有三个参数

    • element 当前数组中正在遍历到的元素
    • index 当前数组中正在遍历到的索引
    • array 当前调用 find 方法的数组
  • 执行回调时用作 this 的对象。

// 筛选出小于10的第一个元素
var arr = [1, 22, 5, 12, 45, 9, 10, 33, 8];
var arr2 = arr.find(function (value) {
  return value < 10;
});
console.log(arr2); //  [1]

// 返回数组中第一个长度大于6的元素
var arr = ["iabc", "iconaff", "bef", "afei", "aiconf"];
var arr2 = arr.find(function (value) {
  return value.length >= 6;
});
console.log(arr2); //  iconaff

可以自己手动封装一个简单版的 find 方法

# 七、数组排序

方法 描述 是否更改变原数组
sort() 用于对数组的元素进行排序
// 无函数
sort();
// 内联比较函数  函数可以是匿名的
sort(function compareFn(a, b) {
  /* … */
});

# 1、sort()未指字函数参数

TIP

当 sort 中没有给定参数时,数组中的元素会先被转换为字符串,然后比较的时候是按 Unicode 编码来进行排序

英文字母对应的 Unicode 编码

  • A~Z 对应 65~ 90 也就是AUnicode编码是65ZUnicode编码是 90
  • a~z 对应 97~122
  • 0~9 对应 48~57
var arr = ["A", "Z", "b", 1, 80, 9, 100, 70, "a"];
arr.sort();
console.log(arr); // [1, 100, 70, 80, 9, 'A', 'Z', 'a', 'b']

注:

在没有函数做为参数时,这种比较本质上是没有办法做数字数组的排序

# 2、sort()指定函数参数

sort(function compareFn(a, b) {
  return a - b;
});

// sort中的函数,也可以是匿名函数
sort(function (a, b) {
  return a - b;
});
  • compareFn 用来指定按某种顺序进行排列的函数
  • a,b 是用于比较的两个元素
  • 函数返回值为a-b,则数组按升序排序,即从小到大
  • 函数返回值为b-a,则数组按降序排序,即从大到小
var arr = [1, 4, 90, 2, 5, 80, 100, 28];
arr.sort(function (a, b) {
  return a - b;
});
console.log(arr); // [1, 2, 4, 5, 28, 80, 90, 100]

如何理解:a-b 即升序,b-a 即降序

在之前的数组中我们学过冒泡排序,冒泡排序的原则就是如果相邻的两个数(a,b)比较时

  • 如果 a-b 大于 0,交换两者位置,即大的数 a 的放后面,小的 b 放前面,升序
  • 如果 b-a 大于 0 ,交换两者位置,即大的数 b 放在前面,小的数 a 放在后面,降序

所以你可以把这里的 a 和 b 当成数组成相邻的两个比较元素

详细参考 MDN 文档 (opens new window)

  • 如果 compareFn(a, b) 大于 0 , b 会被排列到 a 之前。
  • 如果 compareFn(a, b) 小于 0 ,那么 a 会被排列到 b 之前;
  • 如果 compareFn(a, b) 等于 0 , a 和 b 的相对位置不变。

# 3、冒泡排序法封装成函数

// 封装冒泡排序
function bubbleSort(arr, fn) {
  // 数组长度
  var len = arr.length;
  // 当前是否是有序的
  var isSorted;
  var flag = 0;
  // 有序的边界
  var sortBorder = len - 1;
  // 外层for控制交换轮数
  for (var i = 0; i < len - 1; i++) {
    // 内层for控制每一轮,元素交换次数处理
    isSorted = true; // 有序标记,每轮开始默认为有序,如果一旦发生交换,就会变成flag=false,无序

    for (var j = 0; j < sortBorder; j++) {
      if (fn(arr[j], arr[j + 1]) > 0) {
        // 交换两元素位置
        var tmp; // 用来交换两个变量的中间变量
        tmp = arr[j];
        arr[j] = arr[j + 1];
        arr[j + 1] = tmp;
        isSorted = false;
        // 把无序列表的边界,更新为最后一次交换元素的位置
        flag = j;
      }
    }
    if (!isSorted) {
      sortBorder = flag;
    }
    // 这一轮多次交换下来,flag没有变为false,说明没有发生元素交换,此时数组已是有序的
    if (isSorted) {
      break; // 退出最外层for循环
    }
  }
}

// 排序数组
var arr = [2, 3, 4, 1, 5, 6, 7, 8, 9, 35, 76];
bubbleSort(arr, function (a, b) {
  return a - b; // 升序
});
console.log(arr); // [1, 2, 3, 4, 5, 6, 7, 8, 9, 35, 76]

bubbleSort(arr, function (a, b) {
  return b - a; // 降序
});
console.log(arr); // [76, 35, 9, 8, 7, 6, 5, 4, 3, 2, 1]

# 八、数组相关方法总结

总结:

  • 有哪些可以操作数组的方法 ?
  • 这些方法的作用是什么 ?
  • 哪些方法会改变原组数,哪些不会 ?
  • (经典面试题)
实例方法 描述 是否改变原数组 返回结果
push() 将一个或多个元素添加到数组的末尾,并返回该数组的新长度 原数组的新长度
pop() 从数组中删除最后一个元素,并返回该元素的值 返回被删除元素
unshift() 将一个或多个元素添加到数组的头部,并返回该数组的新长度 原数组的新长度
shift() 从数组中删除第一个元素,并返回该元素的值 被删除元素
slice() (查询):提取原数组的一部分并返回一个新数组 不会 将查询到元素组成的新数组返回
splice() (新增):在指定位置插入一项或多项新元素
(删除):在指定位置删除指定项
(更新):在指定位置删除指定项,同时在此处添加一项或多项
返回被删除元素组成的数组
join() 将一个数组的所有元素以特定的某种字符连接成一个字符串
不会 并返回连接后的字符串
indexOf() 返回在数组中可以找到给定元素的第一个索引,如果不存在,则返回 -1。 不会 返回元素索引或-1
includes() 法用来判断一个数组是否包含一个指定的值,
根据情况,如果包含则返回 true,否则返回 false
不会 返回值为 true 或 false
concat() 方法用于合并两个或多个数组。
此方法不会更改现有数组,而是返回一个新数组
不会 将合并成的新数组返回
reverse() 方法将数组中元素的位置颠倒,并返回该数组
该方法会改变原数组。
返回置返后的原数组
sort() 对数组中的元素进行排序(降序或升序) 返回排序后的原数组
filter 返回所有通过函数测试成功的元素
其返回值为测试成功的元素组成的新数组
不会 返回符合要求的所有元素
find 返回数组中满足提供的测试函数的第一个元素的值
否则返回 undefined (opens new window)
不会 返回符合要求的第一个元素
forEach forEach()方法对数组的每个元素执行一次给定的函数 返回值为undefined

关于操作数组的方法还有很多,我们目前只学习到这些,还有更多的在 ES6 之后再讲

# 九、综合应用案例

TIP

结合本章节所学内容进行综合实践应用。

# 1、求数组中每一项的总和、平均数

// 思路:求数组的和,先定义累加器 sum

var socreArr = [99, 96, 95, 89, 86, 88, 67, 53, 69, 36];

// 累加器
var sum = 0;
// 遍历数组,每遍历一个数字,就要把这个数字累加到累加器中
for (var i = 0; i < socreArr.length; i++) {
  sum += socreArr[i];
}

// 数组的总和
console.log(sum); // 778
// 数组的平均数:数组的总和/数组的长度
console.log(sum / socreArr.length); // 77.8

# 2、随机样本:请随机从原来数组中取 3 项

TIP

随机取样,你不能取完样之后,把原数组中的数组给删除了

下面这种方式就是动了原数组

// 数组没有重复项并且不少于三个元素
var arr = [2, 1, 5, 8, 6, 9, 4, 3, 7];

// 结果数组
var result = [];

// 遍历原数组
for (var i = 0; i < 3; i++) {
  // 随机选择一项的下标,数组的下标 0 ~ arr.length - 1;
  // 之前学过一个 random的公式,[a,b] 区间的随机数是 parseInt(Math.random() * arr.length)
  var n = parseInt(Math.random() * arr.length);
  // 把这项推入结果数组
  result.push(arr[n]);
  // 删除这项,防止重复被随机到
  arr.splice(n, 1);
}

console.log(result); // [1, 2, 5] 没运行一次,结果都不一样
  • 随机取样,不会动原数组
// 不动原数组
function sample(arr, k) {
  if (Array.isArray(arr)) {
    // 取样长度不能超过数组长度  数组长3,取3,则3%3=0, 所以要3%4
    k = k % (arr.length + 1);
    // 用来放取样的数组
    var result = [];
    //
    while (result.length < k) {
      // 生成数组下标的随机数
      var n = (Math.random() * arr.length) >> 0;
      // 判断是否在取出的样本中存在
      if (!result.includes(arr[n])) {
        result.push(arr[n]);
      }
    }
    return result;
  }
}

var arr = [2, 1, 5, 8, 6, 9, 4, 3, 7];
console.log(sample(arr, 3));

# 3、随机抽奖

TIP

  • 在我们做抽奖效果时,我们肯定是需要控制一,二,三等奖的中奖概率。
  • 以下是一次活动中,规定的各种奖项的中奖概率(奖项的中奖机会占总数的百分比)
1 等奖 2 等奖 3 等奖 未中奖
概率 5% 10% 20% 65%
奖品 平板电脑 小米手机 电风扇 谢谢惠顾

解题思路:

  • 我们可以设置一个权重数组[5,10,20,65]
  • 然后把权重数组中的中奖概率加起来,做为生成随机数的最大范围
  • 然后把生成的随机数添加到数组的尾部,然后再对数组进行升序排序
  • 最后确定生成随机数在权重数组中的下标,来确定对应的中奖项
  • 如果下标超过原数组长度-1,则以原数组长度-1 为准。
// 随机抽奖
function randomDrawing(prizeWeight, prizes) {
  // 输入的不是数组,就直接返回
  if (!Array.isArray(prizeWeight) || !Array.isArray(prizes)) return;
  // 两数组长度不一样长,或为空时,直接返回
  if (
    prizeWeight.length == 0 ||
    prizes.length == 0 ||
    prizeWeight.length !== prizes.length
  )
    return;
  // 计算所有权重之和
  var sum = 0;
  for (var i = 0; i < prizeWeight.length; i++) {
    sum += prizeWeight[i];
  }
  // 生成0-权重之和之间的随机数(不用加1,从0开始算的)
  var random = (Math.random() * sum) >> 0;

  // 把生成的随机数合并到原数组中,返回一个新中奖数组
  var newArr = prizeWeight.concat(random);
  // 对中奖数组进行排序
  newArr.sort(function (a, b) {
    return a - b;
  });
  // 找查生成的随机数在中奖数组中的下标
  var prizesIndex = newArr.indexOf(random);

  // 随机数下标不能超过原数组的长度-1,超过就以原数组length-1为准
  randomIndex = Math.min(prizesIndex, prizeWeight.length - 1);
  // 取出对应的奖项
  return prizes[randomIndex];
}
// 奖品的权重数组
var prizeWeight = [5, 10, 20, 65];
// 对应的中奖奖品
var prizesArr = ["平板电脑", "小米手机", "电风扇", "谢谢惠顾"];

// 随机抽奖100次,看对应每种物品中奖概率
var arr = [0, 0, 0, 0];
for (var i = 0; i < 100; i++) {
  var prize = randomDrawing(prizeWeight, prizesArr);
  switch (prize) {
    case "平板电脑":
      arr[0]++;
      break;
    case "小米手机":
      arr[1]++;
      break;
    case "电风扇":
      arr[2]++;
      break;
    default:
      arr[3]++;
  }
}
console.log(arr);

# 4、手写 Flatten 方法,实现数组的扁平化 (面试题)

  • 方法一
var arr = [1, 2, 3, [4, [5], [6]], 7, 8];
function flatten(arr) {
  var newArr = [];
  for (var i = 0; i < arr.length; i++) {
    if (Array.isArray(arr[i])) {
      // 是数组,递归调用
      newArr = newArr.concat(flatten(arr[i])); // concat返回新的拼接好的数组
    } else {
      newArr.push(arr[i]);
    }
  }
  return newArr;
}
console.log(flatten(arr));
  • 方法二:forEach+递归
var arr = [1, 2, 3, [4, [5], [6]], 7, 8];
function flatten(arr) {
  var newArr = [];
  arr.forEach(function (el) {
    if (Array.isArray(el)) {
      // 是数组,递归调用
      newArr = newArr.concat(flatten(el)); // concat返回新的拼接好的数组
    } else {
      newArr.push(el);
    }
  });
  return newArr;
}
console.log(flatten(arr));
  • 方法三
    • 循环遍历数组中的每个元素,如果不是数组,就添加到结果数组中
    • 如果是数组,就遍历数组中的每个元素,然后添加到结果数组中
var arr = [1, 2, 3, [4, [2, 3]], [6], 7, 8];
function flatten(arr) {
  var result = [];
  function fn(arr) {
    for (var i = 0; i < arr.length; i++) {
      var item = arr[i]; // 重点
      if (Array.isArray(arr[i])) {
        fn(item); // 递归
      } else {
        result.push(item);
      }
    }
  }
  fn(arr);
  return result;
}

console.log(flat(arr));

# 十、作业

homework

  • 掌握本节课学习的所有数组相关的操作方法
  • 自己手动封装简单版的以下方法:

push、pop、shift、unshift、slice、splice、join、indexOf、lastIndexOf、includes、concat、reverse、find

# 十一、总结重难点

TIP

总结本章重难点知识,理清思路,把握重难点。并能轻松回答以下问题,说明自己就真正的掌握了。

用于故而知新,快速复习。

# 1、重点

TIP

课程中讲到的所有数组相关的方法,都必需要掌握

# 2、难点

TIP

  • 数组去重
  • 手动封装 filter、find、forEach 方法
  • 手写 flat 方法
上次更新时间: 9/22/2023, 7:57:09 PM

大厂最新技术学习分享群

大厂最新技术学习分享群

微信扫一扫进群,获取资料

X