Skip to content

数组介绍

同大多数编程语言一样, 数组是多个数据的容器对象, 用于描述一系列有序数据的组合

创建数组

js
// 创建数组
const items = new Array();
const books = [];

// 创建指定长度的数组
const week = new Array(5);

遍历数组

js
// for/while遍历
const nums = [1, 3, 5, 7];
for (let i = 0; i<items.length; i++) {
  const item = items[i];
  console.log(item);
}

// for of 遍历
const strs = ['a', 'b', 'c', 'd'];
for (let item of strs) {
  console.log(item);
}

基础学习

声明数组

  1. var arr = [] 推荐, 使用数组字面量
  2. var arr = new Array() 通过系统内置的构造函数 Array
  3. var arr = Array() 不使用, 虽然可以,

三种声明方式的数组都继承: Array.prototype 也就是说, 所有的数组实例对象都能使用 Array.prototype 上的所有方法, 同理: 对象类型也是这样的

Array 与 Object 类型的关系

Array 是特殊的 Object

对象底层也是用 obj[key] 来取值的, 数组取值就是利用这种机制

javascript
// 数组的每一个元素对应了一个下标(也叫索引), 下标从0开始
var arr = [1, 2, 3];
// 索引值: [0, 1, 2];

// 模拟数组:
var obj = {
 0: 1,
  1: 2,
  2: 3,
  length: 3,
};

console.log(arr[1] === obj[1]);

arr.hello = "world";
obj.hello = "world";
console.log(arr.hello === obj.hello); // true

稀松数组

数组的元素含有 empty 的数组就叫做 稀松数组

  1. 如果数组最后一位是 empty 那么,那么不会计算最后一位
  2. 如果使用构造函数来创建数组, 就不能有 empty 的元素, 因为 new Array, 传的是参数, 参数不能随便乱写, 会语法错误
javascript
var arr = [,1,2,,,3,4,]; // length: 7
console.log(arr);

Array 构造函数创建数组注意点

  1. 如果只有一个参数, 并且是一个 正整数 (比如: 3) , 它会直接创建一个长度为3, 元素全部是 empty 的数组
  2. 如果只有一个参数, 这个数是一个number类型, 但是不是一个 正整数 就会报错, 比如 Array(-5) Array(1.2)
  3. 如果只有一个参数, 并且这个数不是number类型, 他会创建一个数组, 并且把这个参数作为数组的第一个元素
  4. 如果有多个参数, 他会直接把这些参数全部放到一个数组中, 然后返回
javascript
var arr1 = new Array(3); // [ empty, empty, empty ]
var arr2 = new Array(-1); // 报错: RanageError: Invalid array length
var arr3 = new Array(1.2); // 报错: Invalid array length
var arr4 = new Array('a'); // ['a']
var arr5 = new Array(1, 2, 3, 4); // [ 1, 2, 3, 4 ]

Array 原型上的方法(改变原数组)

数组的方法都是从哪里来的?

结论: 所有的方法都是继承自Array构造函数的原型对象(Array.prototype)上的方法, 原因如下:

  1. 所有的数组实例对象都是有 Array 这个构造函数创建的
  2. 实例对象本身没有这些函数, 就会沿着原型链去查找
  3. 实例对象去找其构造函数的原型对象上的方法, 肯定是能找到的

添加元素: push/unshift

方法名作用效果返回值是否改变原数组
push在数组的最后面添加元素新的 length
unshift在数组的最前面添加元素新的 length
javascript
var arr = [2,3];

console.log(arr.unsfhit(1)); // 3
console.log(arr);  // [1, 2, 3]

console.log(arr.push(4, 5)); // 5
console.log(arr); // [1, 2, 3, 4, 5]

弹出元素 pop/shift

方法名作用效果返回值是否改变原数组
pop弹出数组最后一个元素被弹出的元素
shift弹出数组第一个元素被弹出的元素
javascript
var arr = [1,2,3];

console.log(arr.pop()); // 3
console.log(arr); // [1, 2]

console.log(arr.shit()); // 1
console.log(arr); // [2]

数组元素排序

方法名作用效果返回值是否改变原数组
reverse翻转数组元素位置翻转后的数组
sort对数组排序undefined
reverse 翻转数组顺序
javascript
var arr = [1, 2, 3];
arr.reverse();
console.log(arr); // [3, 2, 1]


var pets = ['cat', 'dog', 'rabbit'];
pets.reverse();
console.log(pets); // ['rabbit', 'dog', 'cat']
sort 排序

sort(callbaack) : 数组排序, 返回排序后的数组, 会改变原数组

  1. 按照元素 ASCII 码, 十进制的值来排序
  2. 默认是升序排序(假设元素全部是number类型)
  3. 如果想指定倒序排序, 可以传入一个检测函数
    1. 这个函数有二个参数 a, b
    2. 必须 return 一个number类型的值
    3. 如果return的值是负数, a 就排在 b 前面
    4. 如果return的值是正数, b 就排在 a 前面
    5. 如果return的值是 0, 则顺序保持不懂
javascript
var arr = [28, 36, 5, 6, 8];

arr.sort(); // 默认根据 ASCII 码排序
console.log(arr); // 结果并不是我们想要的效果: [28, 36, 5, 6, 8]

arr.sort(function(a, b) {
 if (a > b) {
    return -1; // 如果return的值是负数, a 就排在 b 前面
  } else {
   return 1; // 如果return的值是正数, b 就排在 a 前面
  }
});

替换数组元素 splice

splice(start, deleteCount?, ...items?) :剪切或者替换数组现有的元素, 返回被剪切的值, 改变原数组

javascript

var arr = ["a", "b", "c", "d", "e"];

// 1. 从下标值为 start 的位置开始剪切, 如果没有后面两个参数, 则默认剪切到最后
console.log(arr.splice(2)); // 从开始位置截取到最后: ['c', 'd', 'e']
console.info(arr); // ['a', 'b']


// 2. 剪切 deleteCont 个元素, 如果没有 items 参数, 则直接返回被剪切的元素
arr = ["a", "b", "c", "d", "e"];
console.log(arr.splice(2, 2)); // 从开始位置截取2个: ['c', 'd']
console.info(arr); // ['a', 'b', 'e']


// 3. 剪切 deleteCount 个元素, 如果有 items 参数, 则用items参数替换(从start开始替换), 然后返回被剪切的元素
arr = ["a", "b", "c", "d", "e"];
console.info(arr.splice(2, 2, 1, 2, 3)); // 从开始位置, 截取一个, 然后替换成 1,2,3
console.info(arr); // ['a', 'b', 1, 2, 3, 'e']

Array 原型上的方法(不改变原数组)

concat 合并数组

合并两个数组, 并且返回合并后的新数组

javascript
var arr1 = [1, 3, 5];
var arr2 = [2, 4, 6];
var arr3 = arr1.concat(arr2);

console.log(arr1); // [1, 3, 5]
console.log(arr2); // [2, 4, 6]
console.log(arr3); // [1, 3, 5, 2, 4, 6]

slice 截取数组元素

slice(start, end) 截取数组中某个位置的元素,并返回截取的元素

  1. start: 从哪个下标开始截取,如果是负数则从后往前截取, 如果没有 end 参数, 则截取到最后
  2. end: 截取到那个下标, 但是不包括这个下标
javascript
var arr1 = ['a', 'b', 'c', 'd', 'e'];


console.log( arr1.slice(3) ); // ['d', 'e'], 从开始位置开始截取到最后

console.log( arr1.slice(1, 3) ); // ['b', 'c'], 结果没有包含下标为3的元素(不包括3)

console.log( arr1.slice(-3) ); // ['c', 'd', 'e'], 从-3开始截取到最后

console.log( arr1.slice(-4, 3) ); // ['b', 'c'], 从-4位置开始截取到索引为3的位置(不包括3)

console.log( arr1.slice(-4, -2) ); // ['b', 'c'], 从-4位置开始截取到索引为-2的位置(不包括-2)

字符串和数组互转

方法功能返回值是否改变原值
join将数组按指定字符切割为字符串字符串不改变
split将字符按指定字符串切割为数组数组不改变
javascript
// 数组 -> 字符串
var arr = ['a', 'b', 'c', 'd'];
var str = arr.join('-');
console.log(str); // a-b-c-d

// 字符串 -> 数组
var str = "a-b-c-d";
var arr = str.split('-');
console.log(arr); // ['a', 'b', 'c', 'd'];

// split 第二个参数: 限制返回的数组长度
var str = "a-b-c-d";
var arr = str.split('-', 2);
console.log(arr); // ['a', 'b']

类数组

什么是类数组

  1. 是一个类似于数组对象, key的是索引值, 并且有 length  属性
  2. 没有 Array 构造函数原型对象上的方法
  3. arguments对象就是一个典型的类数组对象 Array-Like
javascript
// 以下就是一个类数组
var arrayLike = {
  0: 'a',
  1: 'b',
  2: 'c',
  length: '3'
};

// arguments 对象也是一个类数组

// document.querySelectAll 返回的也是一个类数组
document.querySelectAll('div');

练习题1: 分析下面的代码会输出什么

Q: 控制台会打印出什么

A: 打印出一个对象 {empty, empty, 2:1, 3:2, length:4, push: function, splice: function}

javascript
var obj = {
  '2': 3,
  '3': 4,
  'length': 2, // 注意这个值, 然后再结合下面连接中源码的实现
  'push': Array.prototype.push,
  'splice': Array.prototype.splice
};

obj.push(1);
obj.push(2);
console.log(obj);

// 结果: { empty, empty, 2: 1, 3:2, length:4, push:funciotn() {}, splice: function() {} }

// 在答题之前建议先看: https://www.yuque.com/liaohui5/js-base/xg1g4t#OOIUx

解题思路:

  1. obj.push(1)  此时会执行 obj[obj.length] = 1;  也就是覆盖 obj[2]   的值, 将值修改为 1  并且把 length  修改为 3 ,因为数组的索引必须从0开始,如果前两个位置没有值就是 empty 元素
  2. obj.push(2) 此时会执行 obj[obj.length] = 2 也就是会覆盖 obj[3]  的值(因为执行第一步的时候已经把 length 修改为 3 了), 所以会将obj[3]值修改为 2

练习2: 数组的随机排序(打乱数组)

javascript
/**
 * 数组随机排序(打乱数组)
 * @param {Array} arr 数组
 * @returns {array} 排序后的数组
 */
function shuffle(arr) {
 return arr.sort(() => Math.random() - 0.5);
}

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

/*
1. Math.random() 会返回一个0-1随机数, 不包括0和1, 也就是说, 可能大于0.5, 也可能小于0.5
2. 如果大于0.5那么减去0.5则会返回一个正数
3. 如果小于0.5那么减去0.5则会返回一个负数
4. 也就是说, 每次排序的时候, 都是随机排序的
*/

练习3: 使用 splice 方法实现 unshift 方法

javascript
/**
 * 手动实现unshift方法
 * @param  {...any} args 元素
 * @returns array
 */
Array.prototype.$unshift = function (...args) {
  for (var i = 0, l = args.length; i < l; i++) {
    this.splice(i, 0, args[i]);
  }
  return this;
};

var arr = ["a", "b", "c"];
arr.$unshift(1, 2, 3);
console.info(arr); // [1, 2, 3, "a", "b", "c"]

练习4: 按照直接数排序数组元素

请按照字节数排序以下数组元素: ['我爱你', 'OK', 'hello', '你说What', '可以']

javascript
// 获取字符串的字节数
function getByteSize(str) {
  var str = new String(str);
  var len = str.length;
  if (len === 0) {
    return 0;
  }

  var bytes = 0;
  for (var i = 0; i < len; i++) {
    if (str[i].charCodeAt() <= 255) {
      bytes += 1;
    } else {
      bytes += 2;
    }
  }
  return bytes;
}


var arr = ['我爱你', 'OK', 'hello', '你说What', '可以'];

arr.sort(function (a, b) {
  return getByteSize(a) - getByteSize(b)
});

console.info(arr); // ["OK", "可以", "hello", "我爱你", "你说What"]
// bytes:               2      4       5        6         8

/*
假设: a='我爱你' -> bytes: 6, b='OK' -> btyes: 2
6-2=4 所以会返回正数

假设: a='OK' -> bytes: 2, b='hello' -> bytes: 5
2-5=-3 所以会返回负数
*/

课后5: 数组去重

实现数组去重方法

javascript
/**
 * 数组去重(indexOf)
 * @returns
 */
Array.prototype.$unique = function () {
  var arr = [];
  var item, type;
  for (var i = 0, l = this.length; i < l; i++) {
    item = this[i];
    type = typeof item;

    if (type && type === "object") {
      // reference value
      arr.push(item);
      continue;
    }

    if (arr.indexOf(item) === -1) { // 判断一个值是否存在数组中
      arr.push(item);
    }
  }
  return arr;
};

var arr = [1, 2, 1, 2, "str", "str", false, false, new Date(), new Date()];
console.log(arr.$unique());

/*
console output: 

[
  0: 1
  1: 2
  2: "str"
  3: false
  4: Tue Jul 13 2021 19:28:13 GMT+0800 (中国标准时间) {}
  5: Tue Jul 13 2021 19:28:13 GMT+0800 (中国标准时间) {}
  length: 6
]

*/


// ----------------------------------------------------------------------- //


/**
 * 数组去重2(利用对象的属性)
 * @returns
 */
Array.prototype._unique = function () {
  var temp = {};
  var uniqueArray = [];
  var item;
  for (var i = 0, l = this.length; i < l; i++) {
    item = this[i];
    if (item && typeof item === "object") {
      // reference value
      uniqueArray.push(item);
      continue;
    }

    if (!temp.hasOwnProperty(item)) {
      // unique array
      temp[item] = item;
      uniqueArray.push(item);
    }
  }

  return uniqueArray;
};

var arr = [
  0,
  0,
  false,
  false,
  1,
  2,
  1,
  2,
  "a",
  "a",
  new Date(),
  new Date(),
  {},
  [],
  {},
];

arr = arr._unique(arr);

console.info(arr);

课后6: 写一个 _typeof 精准判断数据类型

手动实现 typeof 方法, 能够准确的返回以下几种类型 string number boolean null undefined function object object-number object-boolean object-string

javascript
/**
 * 获取任意值的准确类型
 * @param {*} val 任意类型的值
 * @returns {string}
 */
function _typeof(val) {
  var type = typeof val;
  if (type && type !== "object") {
    return type;
  }

  type = Object.prototype.toString.call(val); // [object xxx]
  type = type.slice(8, -1).toLowerCase();

  if (type === "object" || type === "array") {
    return type;
  }

  if (type === "number" || type === "string" || type === "boolean") {
    return "object-" + type;
  }

  return type;
}


console.log(_typeof([1, 2, 3])); // array
console.log(_typeof({ name: "hello" })); // object
console.log(_typeof(new Number(1))); // object-number
console.log(_typeof(new Boolean(false))); // object-boolean
console.log(_typeof(new String("hello"))); // object-boolean
console.log(_typeof(new Date())); // date

练习7: 计算一个字符串中单个字符出现的次数

javascript
function getCharCounts(str) {
  if(typeof str !== "string") {
    throw new TypeError("the parameter must be a string");
  }
  var charItem, charMap = {},
  for (var i =0, len = str.length; i<len; i++) {
    charItem = str[i];
    if(charMap[charItem]) {
      charMap[charItem] += 1;
    } else {
      charMap[charItem] = 1;
    }
  }
  return charMap;
}

数组方法

操作方法

函数名返回值作用是否改变原数组
sliceArray截取原数组中 n 个元素并返回一个新数组N
concatArray合并两个数组并返回一个新数组N
spliceArray截取/增加 n 个数组元素Y
popany删除数组最后一项并返回Y
pushNumber最素组末尾添加一项并返回数组改变后的 lengthY
shiftany删除数组最后一项并返回Y
unshiftNumber在数组最前添加一项并返回改变后数组的 lengthY

排序方法

函数名返回值作用是否改变原数组
sortundefined对数组进行排序(默认升序)Y
reverseArray翻转数组顺序N

转换方法

函数名返回值作用是否改变原数组
joinString将数组通过指定符号拼接成字符串N

迭代方法

函数名返回值作用是否改变原数组
someBoolean对数组每一项都运行测试函数, 如果至少有 1 个元素符合要求,就返回 true,否则返回 falseN
everyBoolean对数组每一项都运行测试函数, 如果全部符合要求就返回 true, 否则返回 falseN
forEachundefined对数组每一项都运行传入的函数,没有返回值N
filterArray对数组每一项都运行传入的函数,函数返回 true 的项会组成数组之后返回N
mapArray对数组每一项都运行传入的函数,返回由每次函数调用的结果构成的数组N

copyWithin(target, start, end)

从 start 位置开始(包含 start)复制到 end 位置结束(不包含 end), 然后将这个结果从 target 位置开始替换 不改变数组长度, 返回改变后的数组, 但是会改变原数组

  1. 复制元素: 从 start 索引开始到 end 结束, 但是不包含 end
  2. 替换元素: 从 target 开始, 复制的元素有多少个, 就向后替换多少个
javascript
// 1.正常截取
var arr = ["a", "b", "c", "d", "e"];
arr.copyWithin(0, 1, 3);
console.info(arr);
// 复制元素(1,3): ['b', 'c']
// 替换元素(0): ["a", "b", "c", "d", "e"] => ["b", "c", "c", "d", "e"]

// 2.target 大于 length: 截取到最后
arr = ["a", "b", "c", "d", "e"];
arr.copyWithin(0, 3, 8);
console.info(arr);
// 复制元素(3, 8): ['d', 'e']
// 替换元素(0): ["a", "b", "c", "d", "e"] => ["d", "e", "c", "d", "e"]

// 3.如果start或end是负数, 则从后往前开始计算(length+start, length+end)
arr = ["a", "b", "c", "d", "e"];
arr.copyWithin(0, -3, -1);
console.info(arr);
// 复制元素(2, 4): ["c", "d"]
// 替换元素(0): ["a", "b", "c", "d", "e"] => ["c", "d", "c", "d", "e"]

// 4.如果没有指定复制元素的索引, 默认从0开始
arr = ["a", "b", "c", "d", "e"];
arr.copyWithin(3);
console.info(arr);
// 复制元素: ["a", "b", "c", "d", "e"]
// 替换元素(3), 从3开始替换到最后: ["a", "b", "c", "d", "e"] => ["a", "b", "c", "a", "b"]

// 5. 如果没有指定结束位置, 直接从开始位置复制到最后
arr = ["a", "b", "c", "d", "e"];
arr.copyWithin(2, 3);
console.info(arr);
// 复制元素: ["d", "e"]
// 替换元素(3), 从3开始替换到最后: ["a", "b", "c", "d", "e"] => ["a", "b", "d", "e", "e"]

手动实现 copyWithin

javascript
/**
 * 手动实现 copyWithin
 * @param {Number} target 替换开始位置
 * @param {Number} start 剪切开始位置
 * @param {Number} end 剪切结束的位置
 */
Array.prototype.$copyWithin = function (target) {
  var O = Object(this);
  var len = this.length;

  // 将target转number类型
  var relativeTarget = target >> 0;

  // to: 正整数(如果小于0:(如-2: len + -2), 如果大于0: 最大值为 len)
  var to =
    relativeTarget < 0
      ? Math.max(len + relativeTarget, 0)
      : Math.min(relativeTarget, len);

  // 将开始替换坐标装number类型
  var relativeStart = start >> 0;
  var from =
    relativeStart < 0
      ? Math.max(len + relativeStart, 0)
      : Math.min(relativeStart, len);

  // 如果 end !== undefined 说明传值了, 需要转数字
  var end = arguments[2];
  var relativeEnd = end === undefined ? len : end >> 0;
  var final =
    relativeEnd < 0
      ? Math.max(len + relativeEnd, 0)
      : Math.min(relativeEnd, len);

  // Step 15.
  var count = Math.min(final - from, len - to);

  // Steps 16-17.
  var direction = 1;

  if (from < to && to < from + count) {
    direction = -1;
    from += count - 1;
    to += count - 1;
  }

  // Step 18.
  while (count > 0) {
    if (from in O) {
      O[to] = O[from];
    } else {
      delete O[to];
    }

    from += direction;
    to += direction;
    count--;
  }

  // Step 19.
  return O;
};

entries

返回数组的迭代器对象

javascript
var arr = ["a", "b", "c"];
var it = arr.entries();

// 与普通生成器函数生成的迭代器不一样的是: value: [索引, 值], value 的值是一个数组
console.info(it.next()); // {value: [0, "a"], done: false}
console.info(it.next()); // {value: [0, "b"], done: false}
console.info(it.next()); // {value: [0, "c"], done: false}
console.info(it.next()); // {value: undefined, done: true}

为什么 for of 默认可以迭代数组却不能迭代对象

for of  只能迭代原型上有 [Symbol.iterator]  这个属性的对象

javascript
var obj = {
  0: "a",
  1: "b",
  2: "c",
  length: 3,
};

// 必须需要将obj代码改成这样就可以迭代, 否则就会报错: TypeError: obj is not iterable
obj[Symbol.iterator] = Array.prototype[Symbol.iterator];
// Reflect.set(obj, Symbol.iterator, Reflect.get(Array, Symbol.iterator))
for (item of obj) {
  console.info(item);
}

entries 练习

排序一个二维数组中的每一个数组

javascript
let arr = [
  [11, 24, 13, 44],
  [23, 15, 33, 46],
  [12, 23, 33, 34],
];

// 二维数组排序
function sortArray(arr) {
  const it = arr.entries();
  let hasNext = true;

  while (hasNext) {
    const item = it.next();
    if (item.done) {
      hasNext = false;
      break;
    }
    item.value[1].sort((a, b) => a - b);
  }
  return arr;
}

console.info(sortArray(arr));

/*
输入的结果:
[
  [11, 13, 24, 44],
  [15, 23, 33, 46],
  [12, 23, 33, 34]
]
*/

fill(val, start, end)

fill 方法是根据下标范围( 从start开始到end结束, 不包括end )覆盖新的值 val , 这个方法会改变原数组

javascript
let arr = [1, 2, 3, 4, 5];

// 1.正常替换
arr.fill("a", 2, 4);
console.info(arr); // [1, 2, 'a', 'a', 5]

// 2.不指定end: 从start位置开始, 替换到最后
arr.fill("b", 2);
console.info(arr); // [1, 2, "b", "b", "b"]

// 3.不指定start和end: 全部替换
arr.fill("c");
console.info(arr); // ["c", "c", "c", "c", "c"]

// 4.不指定参数: 全部替换成undefined
arr.fill();
console.info(arr);
// [undefined, undefined, undefined, undefined, undefined]

// 5. start===end 或者star和end不是数字: 不会执行替换
arr.fill("a", 1, 1);
arr.fill("a", "b", "c");
console.info(arr);

find/findIndex(callback(item, index, array), thisArg)

find: 返回第一个满足回调函数返回的条件的元素, 如果没有符合回调函数返回的条件, 返回 undefined

findIndex: 返回第一个满足回调函数返回的条件的索引, 如果没有符合回调函数返回的条件, 返回 -1

  1. callback(item, index, array) : 回调函数必须返回一个布尔值, 就算不返回布尔值, 内部也会隐式转换成布尔值
    1. item  当前遍历到的元素
    2. index  当前遍历到的索引
    3. array  指向调用 find  方法的对象
  2. thisArg  可选参数, 可以指定 callback 的 this 指向, 默认情况下: callback 中的 this 指向 window, 严格模式下指向 undefined
javascript
let arr = [1, 2, 3, 4, 5];

const findItem = arr.find((item, index, obj) => {
  console.info(arr === obj); // true
  console.info(arr[index] === item); // true
  return item > 3;
});

console.info(findItem); // 4

let arrLike = {
  0: "a",
  1: "b",
  2: "c",
  length: 3,
};

let item = Array.prototype.find.call(arrLike, (item, index, array) => {
  console.info(arrLike === array); // true
  console.info(item === arrLike[index]); // true
  return item === "c";
});
console.info(item); // c

关于遍历的问题

find 的遍历效率是低于 ES5 的数组扩展方法的, 因为在 遍历稀松数组  的时候, find 方法, 不会跳过 empty  元素, 而其他的 es5 的数组扩展方法会跳过 empty  元素

ES5 数组的其他扩展方法: map forEach filter reduce reduceRight every some

javascript
var arr = ["a", , "c", , "e"];

arr.find((item) => {
  console.log(item); // a undefined c undefined e
});

flat 多维数组转一维数组

将多维数组扁平化, 返回一个新的数组, 不会改变原数组

  1. flat(deep) : deep 必须是一个正整数, 而且必须大于 0 才会生效
  2. 如果原数组是一个稀松数组, 返回的新数组, 会忽略稀松数组的 empty 元素
javascript
let arr = [1, 2, 3, [4, 5, 6], [7, 8, 9]];

// 二维数组
let arr2 = arr.flat();
console.info(arr2);
console.info(arr === arr2);

// 三维数组
let arr = [1, 2, 3, [4, 5, 6, [7, 8, 9]]];
let arr2 = arr.flat(2);
console.info(arr2); // [1,2,3,4,5,6,7,8,9]

// 多维数组(用Infinity而不是去看数组嵌套的深度)
let arr = [1, [2, [3, [4, [5, [6, [7, [8]]]]]]]];
// let arr2 = arr.flat(7);
let arr2 = arr.flat(Infinity);
console.info(arr2); // [1,2,3,4,5,6,7,8]

flatMap(callback(item, index, array), thisArg)

将每次 callback  返回的结果数组, 放到一个新的数组中, 并且扁平化处理

  1. callback:
    1. item: 当前遍历的元素
    2. index: 当前遍历的索引
    3. array: 被遍历的原数组
  2. thisArg: 指定 callback  函数的时候 this 指向, 如果不传这个参数, 默认 this 指向 window,严格模式下是 undefined
javascript
let nums = ["123", "345", "789"];
// 如何快速转换成这样: ["1", "2", "3", "3", "4", "5", "7", "8", "9"]

// 1. map + flat
let arr1 = nums.map((item) => item.split("")).flat();
// ["1", "2", "3", "3", "4", "5", "7", "8", "9"]

// 2. flatMap
let arr2 = nums.flatMap((item, index, array) => {
  console.info(array === nums); // true
  console.info(array[index] === item); // true
  return item.split("");
}); // ["1", "2", "3", "3", "4", "5", "7", "8", "9"]

手动实现 flatMap

本质就是 map + flat

javascript
Array.prototype.$flatMap = function (callback) {
  if (typeof callback !== "function") {
    throw new TypeError("the callback must be a function");
  }

  let thisArg = arguments[1];
  let len = this.length;
  let res = [];
  let items;
  let item;
  for (let i = 0; i < len; i++) {
    item = this[i];
    items = callback.apply(thisArg, [item, i, this]);
    items && res.push(items);
  }
  return res.flat();
};

let nums = ["123", "345", "789"];
let arr = nums._flatMap((item, index, array) => {
  console.info(nums === array);
  console.info(item === nums[index]);
  return item.split("");
});
console.info(arr); // ["1", "2", "3", "3", "4", "5", "7", "8", "9"]

Array.from(arrLike, mapFn(item, index), thisArg)

将一个 类数组对象  或者 可迭代对象  转换成一个新的数组

  1. arrLike: 被转换的类数组对象
  2. mapFn: 转换的时候, 每一项都会被这个函数处理, 然后把这个函数处理后的值放到返回的数组小红
    1. item: 当前项的值
    2. index: 当前遍历到的索引值
  3. thisArg: 指定 mapFn 执行时的 this 指向
javascript
// 类数组--arrLike就是一个标准的类数组对象
var arrLike = {
  0: { id: 101, username: "tom" },
  1: { id: 102, username: "jerry" },
  2: { id: 103, username: "jack" },
  length: 3,
};

var arr = Array.from(arrLike);
console.info(arr); // [{id:101,username:"tom"},{id:102,username:"jerry"},{id:103,username:"jack"}]

// 可迭代对象 -- 字符串对象有 Symbol(Symbol.iterator) 这个属性, 有这个属性的对象: 比如 map set 都是可迭代对象
var str = "hello";
var arr2 = Array.from(str); // 会自动装箱 str -> new String(str)
console.log(arr2); // ["h", "e", "l", "l", "o"]

Array.from + Set 多数组去重

javascript
// 多数组去重
var arr1 = [1, 2, 3, 4, 5];
var arr2 = [2, 3, 4, 5, 6];
var arr3 = [3, 4, 5, 6, 7];

var uniArr = Array.from(new Set(...arr1, ...arr2, ...arr3));
console.info(uniArr); // [1, 2, 3, 4, 5, 6, 7]

Array.from 源码实现

javascript
Array.$from = (function () {
  // 判断是否是一个函数
  var isCallable = function (fn) {
    return typeof fn === "function";
  };

  // 判断一个值是否是构造函数, 箭头函数是无法实例化的, 只有通过 function/class 关键字声明的函数可以实例化
  var isConstructor = function (fn) {
    if (typeof fn !== "function") {
      return false;
    }
    try {
      new fn();
      return true;
    } catch (e) {
      return false;
    }
  };

  // 将一个值转number类型
  var toInteger = function (value) {
    var number = Number(value);
    if (isNaN(number)) {
      return 0;
    }
    if (number === 0 || !isFinite(number)) {
      return number;
    }
    return (number > 0 ? 1 : -1) * Math.floor(Math.abs(number));
  };

  // 将一个值转正整数: 最小0, 最大maxSafeInteger
  var maxSafeInteger = Math.pow(2, 53) - 1;
  var toLength = function (value) {
    var len = toInteger(value);
    return Math.min(Math.max(len, 0), maxSafeInteger);
  };

  // 第一个参数: arrayLike
  //     如果是一个 undefined / null 就直接抛出错误
  //     如果是一个对象(初始值会自动装箱):
  //     有length并且是一个正整数, 就会返回与length值对应个数的数组元素的数组 {length:3} => [e,e,e]
  //     没有length或者length不是一个正整数, 则返回一个空对象 {} => []
  //
  // 第二个参数: mapFn
  //     如果传递了这个参数, 并且是一个可调用的函数, 则所有的元素都会被这个函数处理
  //     var arr = [1,2,3,4];
  //     var fn = (item) => item += 1;
  //     Array.from(arr, fn);
  //     arr 中所有的元素都会执行fn , 然后将执行的结果返回
  //
  // 第三个参数:
  //     可以指定第二个参数执行时的 this 指向
  return function (arrayLike /*, mapFn, thisArg */) {
    // 如果被转换的值是 null/undefined 直接报错
    if (arrayLike == null) {
      throw new TypeError(
        "Array.from requires an array-like object - not null or undefined"
      );
    }

    // 第一个参数items: 即将被转换的值
    // 如果是一个引用值, 则不会进行任何操作
    // 如果是一个初始值, 则调用与之对应的包装类来装箱
    // 12 => new Number(12)
    // false => new Boolean(false)
    // "str" => new String(str)
    var items = Object(arrayLike);

    // 获取第二个参数 mapFn, 如果没传默认就是 undefined
    // 因为arguments是一个类数组对象, 去对象上取一个不存在的key的值, 就是 undefined
    var mapFn = arguments[1];

    // 获取第3个参数: 指定第二个参数(函数)调用时的 this 指向, 默认 undefined
    var thisArg = arguments[2];

    // 如果第二个参数 mapFn 不是undefined && 不是函数, 则直接抛出错误
    if (typeof mapFn !== "undefined" && !isCallable(mapFn)) {
      throw new TypeError(
        "Array.from: when provided, the second argument must be a function"
      );
    }

    // 获取被转换参数的 length, 值是0或正整数
    var len = toLength(items.length);

    // $constructor = this;
    // 调用这个 from 方法的对象: this
    // 判断当前调用这个 from 方法的函数执行上下文是否是一个构造函数, 如果不是一个构造函数直接 new Array(len)
    // 如果是一个构造函数, 就实例化这个函数: new $constructor(len), 获得一个实例对象
    // 一般是这样调用的: Array.from() 此时的 this 指向 Array构造函数
    // 如果这样调用: Array.from.call(null, ...) 此时的 this 就是 null, 此时的3元运算符就会返回 Array 函数
    // 如果这样调用: Array.from.call(Number, ...) 此时的 this 指向 Number 函数, 那就会执行 new Number(len)
    var $constructor = this;
    $constructor = isConstructor(this) ? $constructor : Array;
    var arr = new $constructor(len);

    var key = 0;
    var val;
    while (key < len) {
      // 获取被转换对象items上的key的值, 如果key不存在值就是 undefined
      val = items[key];

      // 如果传递了每一项的处理函数mapFn, 在赋值的时候就调用这个处理函数, 没有传入就直接赋值
      // 调用处理函数式mapFn: 如果传递了 thisArg, 就改变 mapFn 的this指向
      if (mapFn) {
        arr[key] =
          typeof thisArg === "undefined"
            ? mapFn(val, key)
            : mapFn.call(thisArg, val, key);
      } else {
        arr[key] = val;
      }
      key += 1;
    }

    arr.length = len;
    return arr;
  };
})();

includes

判断一个数组中是否包含某个值, 返回一个布尔值

  1. searchItem: 需要搜索的值
  2. formIndex: 从那个位置开始搜索, 如果这个值大于数组的 length 则返回 false
javascript
var arr = ["a", "b", "hello", "word"];
var searchItem = "hello";

if (arr.includes(arr)) {
  console.log("数组中包含" + searchItem);
} else {
  console.log("数组中不包含" + searchItem);
}

数组方法重写

原生就要的方法为什么要重写呢? ?? 重写只是为了更加理解原生方法的运行原理, 有助于学习和记忆

forEach

javascript
/**
 * 遍历数组
 * @param {Function} callback
 * @param {Object} thisArg
 */
Array.prototype.$forEach = function (callback) {
  if (typeof callback !== "function") {
    throw new TypeError("forEach callback must be a function");
  }

  var thisArg = arguments[1] || window;

  for (var i = 0, l = this.length; i < l; i++) {
    callback.call(thisArg, this[i], i, this);
  }
};

filter

javascript
/**
 * 筛选数组(filter)
 * @param {Function} callback
 * @param {any} thisArg
 * @return {Array} res
 */
Array.prototype.$filter = function (callback) {
  if (typeof callback !== "function") {
    throw new TypeError("The filter callback must be a function");
  }

  var thisArg = arguments[1] || window;
  var res = [];
  var item;

  for (var i = 0, l = this.length; i < length; i++) {
    item = this[i];
    if (callback.call(thisArg, item, i, this)) {
      res.push(item);
    }
  }

  return res;
};

map

javascript
/**
 * 遍历数组, 并且返回新的数组, 数组的元素是callback返回的值(map)
 * @param {Function} callback
 * @param {any} thisArg
 * @return {Array} res
 */
Array.prototype.$map = function (callback) {
  if (typeof callback !== "function") {
    throw new TypeError("The filter callback must be a function");
  }

  var thisArg = arguments[1] || window;
  var len = this.length;
  var res = new Array(len);

  for (var i = 0; i < len; i++) {
    res[i] = callback.call(thisArg, this[i], i, this);
  }

  return res;
};

every/some

javascript
/**
 * 检测数组所有项是否符合需求
 * @param {Function} testCallback
 * @param {any} thisArg
 * @return {Boolean}
 */
Array.prototype.$every = function (testCallback) {
  if (typeof callback !== "function") {
    throw new TypeError("The filter callback must be a function");
  }

  var thisArg = arguments[1] || window;

  for (var i = 0, l = this.length; i < l; i++) {
    if (!testCallback.call(thisArg, this[i], i, this)) {
      return false;
    }
  }

  return true;
};

/**
 * 判断数组中所有的项所否有一项/多项符合测试函数
 * @param {Function} callback
 * @param {Object}  thisArg
 * @return {Boolean}
 */
Array.prototype.$some = function (callback) {
  var o = Object(this);
  var len = o.length >>> 0;
  if (len === 0) {
    return false;
  }

  var thisArg = arguments[1] || window;
  for (var i = 0; i < len; i++) {
    if (callback.call(thisArg, o[i], i, o)) {
      return true;
    }
  }

  return false;
};

find/findIndex

javascript
/**
 * 返回检测函数要求的第一个元素,没有找到返回undefined
 * @param {Function} callback
 * @param {any} thisArg
 * @return {Boolean}
 */
Array.prototype.$find = function (callback) {
  if (typeof callback !== "function") {
    throw new TypeError("The filter callback must be a function");
  }

  var thisArg = arguments[1] || window;
  var item;
  for (var i = 0, l = this.length; i < l; i++) {
    item = this[i];
    if (callback.call(thisArg, item, i, this)) {
      return item;
    }
  }

  // return undefined; 默认就会返回 undefined
};

/**
 * 返回检测函数要求的第一个元素的索引,没有找到返回undefined
 * @param {Function} callback
 * @param {any} thisArg
 * @return {Boolean}
 */
Array.prototype.$findIndex = function (callback) {
  if (typeof callback !== "function") {
    throw new TypeError("The filter callback must be a function");
  }

  var thisArg = arguments[1] || window;

  for (var i = 0, l = this.length; i < l; i++) {
    if (callback.call(thisArg, this[i], i, this)) {
      return i;
    }
  }
};

flat

javascript
/**
 * 数组扁平化
 * @return {Array} 结果数组
 */
Array.prototype.$flat = function () {
  var stack = [].concat(this);
  var res = [];
  var item;

  while (stack.length) {
    item = stack.shift();
    if (Array.isArray(item)) {
      stack.push(...item);
    } else {
      res.push(item);
    }
  }

  return res;
};

includes/indexOf/lastIndexOf

javascript
/**
 * 数组是否包含指定的值
 * @return {boolean} 是否包含
 */
Array.prototype.$includes = function (findItem) {
  var len = this.length;
  if (len === 0) {
    return false;
  }

  var n = arguments[1] || 0;
  var fromIndex = Math.max(n >= 0 ? n : len - Math.abs(n), 0); // 正整数

  for (var i = fromIndex; i < len; i++) {
    // 不能用: === 因为 NaN 不等于 NaN
    if (Object.is(findItem, this[i])) {
      return true;
    }
  }
  return false;
};

/**
 * 查询给定值第一次出现的位置
 * @param {any} 要差早的值
 * @fromIndex {Number} 从哪个位置开始查找
 * @return {Number} 结果索引,没找到就-1
 */
Array.prototype.$indexOf = function (findItem) {
  var len = this.length;
  var resultIndex = -1;
  if (len === 0) {
    return resultIndex;
  }

  var n = arguments[1] || 0;
  var fromIndex = Math.max(n >= 0 ? n : len - Math.abs(n), 0); // 正整数

  for (var i = fromIndex; i < len; i++) {
    if (this[i] === findItem) {
      resultIndex = i;
      break;
    }
  }

  return resultIndex;
};

/**
 * 查询给定值最后一次出现的位置
 * @param {any} 要差早的值
 * @fromIndex {Number} 从哪个位置开始查找
 * @return {Number} 结果索引,没找到就-1
 */
Array.prototype.$lastIndexOf = function (findItem) {
  var len = this.length;
  var resultIndex = -1;
  if (len === 0) {
    return resultIndex;
  }

  var n = arguments[1] || len;
  var fromIndex = Math.max(n >= 0 ? n : len - Math.abs(n), 0); // 正整数

  for (var i = fromIndex; i >= 0; i--) {
    if (this[i] === findItem) {
      resultIndex = i;
      break;
    }
  }

  return resultIndex;
};

join/split

字符串数组互相装换

javascript
/**
 * 将数组按照指定字符分割为字符串
 * @return {String} 结果字符串
 */
Array.prototype.$join = function (separator) {
  var tag = ",";
  if (typeof separator !== "undefined") {
    tag = String(separator);
  }

  var str = "";
  var len = this.length;
  if (len === 0) {
    return str;
  }

  var maxIndex = len - 1;
  for (var i = 0; i < len; i++) {
    str += this[i];
    str += i === maxIndex ? "" : tag; // 删除最后一个 tag
  }

  return str;
};

/**
 * 将字符串按照指定格式切割成数组(依赖: substring 方法)
 * @param {String}
 * @return {Array}
 */
String.prototype.$split = function (separator) {
  var str = this;
  var len = this.length;
  if (len === 0) {
    return [""];
  }

  var tag = "";
  if (typeof separator !== "undefined") {
    tag = separator;
  }

  var taglen = tag.length;
  var res = [];
  var item, tagIndex;
  while (str.length) {
    tagIndex = str.indexOf(tag);
    if (tagIndex === -1) {
      // last item
      res.push(String(str));
      break;
    }
    item = str.substring(0, tagIndex);
    item && res.push(item);
    str = str.substring(tagIndex + taglen);
  }

  return res;
};

keys/values/entries

返回一个迭代器对象

javascript
/**
 * 获取keys的迭代器对象
 * @return {Iterator}
 */
Array.prototype.$keys = function () {
  var index = 0;
  var len = this.length;
  return {
    next: function () {
      var done = index >= len;
      var value = done ? undefined : index;
      index++;
      return {
        done: done,
        value: value,
      };
    },
    // 实现迭代器接口
    [Symbol.iterator]: function () {
      return this;
    },
  };
};

/**
 * 获取values的迭代器对象
 * @return {Iterator}
 */
Array.prototype.$values = function () {
  var index = 0;
  var len = this.length;
  var _self = this;
  return {
    next: function () {
      var done = index >= len;
      var value = done ? undefined : _self[index];
      index++;
      return {
        done: done,
        value: value,
      };
    },
    // 实现迭代器接口
    [Symbol.iterator]: function () {
      return this;
    },
  };
};

/**
 * 获取迭代器对象
 * @param {Array}
 * @return {Array}
 */
Array.prototype.$entries = function () {
  var index = 0;
  var len = this.length;
  var _self = this;
  return {
    next: function () {
      var done = index >= len;
      var value = done ? undefined : [index, _self[index]];
      index++;
      return {
        done: done,
        value: value,
      };
    },
    // 实现迭代器接口
    [Symbol.iterator]: function () {
      return this;
    },
  };
};

concat

javascript
/**
 * 合并两个数组并返回新的数组
 * @param {Array}
 * @return {Array}
 */
Array.prototype.$concat = function () {
  var res = [];
  for (var i = 0, l = this.length; i < l; i++) {
    res.push(this[i]);
  }

  for (var k in arguments) {
    arguments.hasOwnProperty(k) && res.push(arguments[k]);
  }

  return res;
};

reverse

javascript
/**
 * 将数组倒转顺序(虽然倒着遍历一遍是最简单的, 但是那样性能不如只遍历一半, 而且不会改变原数组)
 * @return {Array}
 */
Array.prototype.$reverse = function () {
  var o = Object(this); // 转换类型,让 o 必定是一个 object
  var len = o.length >>> 0;
  if (len < 2) {
    return this;
  }

  var swapCount = Math.floor(len / 2);
  var item, snapIndex;
  for (var i = 0; i < swapCount; i++) {
    snapIndex = len - 1 - i;
    item = o[i];
    o[i] = o[snapIndex];
    o[snapIndex] = item;
  }

  return o;
};

fill

javascript
Array.prototype.$fill = function (value) {
  var O = Object(this);

  // 强行将 length 转数字, 防止是 call/apply 这种
  // 调用的传一个带length的对象
  var len = O.length >>> 0;

  // 开始位置
  var start = arguments[1];
  var relativeStart = start >> 0;
  var k =
    relativeStart < 0
      ? Math.max(len + relativeStart, 0)
      : Math.min(relativeStart, len);

  // 结束位置
  var end = arguments[2];
  var relativeEnd = end === undefined ? len : end >> 0;
  var final =
    relativeEnd < 0
      ? Math.max(len + relativeEnd, 0)
      : Math.min(relativeEnd, len);

  // 填充
  while (k < final) {
    O[k] = value;
    k++;
  }

  // 返回填充后的数组
  return O;
};

reduce/reduceRight

javascript
/**
 * reduce: 从左向右遍历
 * @param {Function} callback
 * @param {any} initVal
 */
Array.prototype.$reduce = function (callback) {
  if (typeof callback !== "function") {
    throw new TypeError("The 'callback' must be a function");
  }

  var o = Object(this);
  var len = o.length;

  // 如果没有传初始值, 将数组的第一个值作为初始值, 并且从1开始循环
  var initVal = o[0];
  var startIndex = 1;
  if (arguments.length > 1) {
    // 如果传入了初始值
    initVal = arguments[1];
    startIndex = 0;
  }

  for (var i = startIndex; i < len; i++) {
    initVal = callback(initVal, o[i], i, o);
  }

  return initVal;
};

/**
 * reduceRight
 * @param {Function} callback
 * @param {any} initVal
 */
Array.prototype.$reduceRight = function (callback) {
  if (typeof callback !== "function") {
    throw new TypeError("The 'callback' must be a function");
  }

  var o = Object(this);
  var maxIndex = o.length - 1;

  // 如果没有传初始值, 将数组的最后一个值作为初始值, 并且从倒数第二个开始循环
  var initVal = o[maxIndex];
  var startIndex = maxIndex - 1;
  if (arguments.length > 1) {
    // 如果传入了初始值
    initVal = arguments[1];
    startIndex = maxIndex;
  }

  for (var i = startIndex; i >= 0; i--) {
    initVal = callback(initVal, o[i], i, o);
  }

  return initVal;
};

push/unshift

javascript
/**
 * 向数组的最后添加元素
 * @param  {...any} args 要插入到数组中的元素
 * @returns {number} length 数组的长度
 */
Array.prototype.$push = function (...args) {
  for (let i = 0, l = args.length; i < l; i++) {
    this[this.length] = args[i];
  }
  return this.length;
};

var arr = [1, 2];

console.log(arr.$push(3, 4, 5)); // 5;
console.info(arr); // [1, 2, 3, 4, 5]

/**
 * 向数组的最前面添加元素
 * @param  {...any} args
 * @returns {number}
 */
Array.prototype.$unsfhit = function (...args) {
  let item;
  for (let i = 0, len = args.length; i < len; i++) {
    item = this[i];
    this[i] = args[i];
    this[i + len] = item;
  }
  return this.length;
};

var arr = [1, 2, 3];
console.log(arr.$unsfhit(4, 5, 6)); //6
console.info(arr); // [4, 5, 6, 1, 2, 4]

pop/shift

javascript
/**
 * 弹出数组最后一个元素并且返回
 * @returns {any}
 */
Array.prototype.$pop = function () {
  const max = this.length - 1;
  const lastElement = this[max];
  delete this[max];
  this.length -= 1;
  return lastElement;
};

var arr = [1, 2, 3];
console.info(arr.$pop()); // 3
console.info(arr); // [1, 2]

/**
 * 弹出数组的第一个元素并且返回
 * @returns {any}
 */
Array.prototype.$shift = function () {
  const len = this.length;
  const firstElement = this[0];
  for (let i = 0; i < len; i++) {
    if (i === 0) {
      continue;
    }
    // 从1开始,后面的所有元素向前移动一位
    this[i - 1] = this[i];
  }

  // 前面的元素都向前移动了一位, 所以删除最后一位多余的元素
  delete this[len];
  this.length -= 1;
  return firstElement;
};

var arr = [1, 2, 3];
console.info(arr.$shift()); // 1
console.info(arr); // [2, 3]

splice

删除/替换原数组, 也就是说会改变原数组

javascript
/**
 * 完美实现 splce 函数的效果(改变原数组)
 * @param {start} 开始删除/替换的位置
 * @param {deleteCount} (可选)要剪切的个数
 * @param {any} (可选)要替换的多个参数
 * @return {Array} 剪切的数组
 */
Array.prototype.$splice = function () {
  var o = Object(this);
  if (Object.prototype.toString.call(o) !== "[object Array]") {
    throw new TypeError("'this' must be [object Array]");
  }

  var len = o.length >>> 0;
  var start = arguments[0] >> 0;
  start = start < 0 ? Math.max(0, len + start) : Math.min(len, start);

  // 如果传入了 deleteCount 就转为数字, 如果没传截取到最后
  // deleteCount 只能是 0-len 的正整数
  var deleteCount = arguments.length > 1 ? arguments[1] >> 0 : Infinity;
  deleteCount =
    deleteCount < 0
      ? Math.max(0, len + deleteCount)
      : Math.min(len, deleteCount);

  // 判断是否需要替换, 从第2个位置截取到最后
  var replaces =
    arguments.length > 2 ? Array.prototype.slice.call(arguments, 2) : [];
  var replaceLen = replaces.length;

  // res: 返回值
  var res = [];

  if (replaceLen) {
    // >>> 替换操作:
    // 原数组从插入位置开始, 后面所有的元素向后移动 replaceLen - deleteCount 个位置
    // 必须是从最后开始往前的顺序向后移动, 如果从前向后的顺序, 就可能导致后面的元素被覆盖
    var index = len - 1;
    while (index >= start) {
      o[index + replaceLen - deleteCount] = o[index];
      index--;
    }

    // 从 start 位置开始插入 replaceLen 个项目
    var currentIndex;
    for (var i = 0; i < replaceLen; i++) {
      currentIndex = i + start;
      res.push(o[currentIndex]);
      o[currentIndex] = replaces[i];
    }
  } else {
    // >>> 删除操作:
    // 保存指定位置的元素放入到结果数组res中
    // 从 start + deleteCount 位置开始,后面所有的元素向前移动 deleteCount 个位置
    // index: 当前要移动的元素位置, deleteIndex: 当前要删除的元素位置
    var index = start + deleteCount;
    var deleteIndex;
    while (index < len) {
      currentIndex = index - deleteCount;
      res.push(o[deleteIndex]);
      o[deleteIndex] = o[index];
      delete o[index];
      index++;
    }

    // 删除最后 deleteCount 个空元素
    o.length -= deleteCount;
  }

  return res;
};

slice

javascript
/**
 * slice 截取数组中n个元素
 * @param {Number} 开始下标
 * @param {Number} (可选)结束下标
 * @returns {Array} 截取的值
 */
Array.prototype.$slice = function () {
  var o = Object(this);
  var len = o.length >>> 0;
  if (len === 0) {
    return [];
  }

  // 开始坐标/结束坐标都必须是: 正整数
  var startIndex = arguments[0] >> 0;
  startIndex =
    startIndex < 0 ? Math.max(len + startIndex, 0) : Math.min(len, startIndex);

  var endIndex = arguments.length > 1 ? arguments[1] >> 0 : Infinity;
  endIndex =
    endIndex < 0 ? Math.max(len + endIndex, 0) : Math.min(len, endIndex);

  var res = [];
  for (var i = startIndex; i < endIndex; i++) {
    res.push(o[i]);
  }
  return res;
};

数组去重

两次 for 循环去重

javascript
function uniArray(arr) {
  var res = [];
  var isContain, item;
  for (var i = 0, len = arr.length; i < len; i++) {
    isContain = false;
    item = arr[i];
    // 每遍历一项, 就去res数组中找是否已经存在
    for (var k = 0; k < res.length; k++) {
      if (item === res[k]) {
        isContain = true;
        break;
      }
    }
    // 如果遍历结束都res不存在当前的值, 就直接放入当前的值
    !isContain && res.push(item);
  }
  return res;
}

var arr = [1, 1, 2, 6, 3, 3, 2, 2, 8, 8, 4, 5, 6, 5, 4, 5, 7, 9];
console.info(uniArray(arr).sort()); // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

filter 去重

javascript
function uniArray(arr) {
  // 每次去判断当前item第一次出现的位置是否是当前的索引, 如果是证明没有重复的, 如果不是证明当前item前面有重复的值
  return arr.filter((item, index) => arr.indexOf(item) === index);
}

var arr = [1, 0, 1, 2, 6, 3, 3, 2, 2, 8, 8, 4, 5, 6, 5, 4, 5, 7, 9, 0];
console.info(uniArray(arr).sort()); // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

indexOf 去重

javascript
// 推荐这种, 语法更明确, for 性能略微比 forEach 好
function uniArray(arr) {
  var res = [];
  var item;
  for (var i = 0, l = arr.length; i < l; i++) {
    item = arr[i];
    // 每次循环都判断: res 数组中没有就 item 就直接 res.push(item);
    res.indexOf(item) === -1 && res.push(item);
  }
  
  return res;
}

// 或者这样, 也是可以的
function uniArray(arr) {
  var res = [];
  arr.forEach(item => res.indexOf(item) === -1 && res.push(item));
  return res;
}

var arr = [1, 0, 1, 2, 6, 3, 3, 2, 2, 8, 8, 4, 5, 6, 5, 4, 5, 7, 9, 0];
console.info(uniArray(arr).sort()); // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

includes 去重

原理同 indexOf去重, 都是循环然后去判断当前 item 是否存在一个数组中

javascript
function uniArray(arr) {
  var res = [];
  var item;
  for (var i = 0, l = arr.length; i < l; i++) {
    item = arr[i];
    // 每次判断结果数组中是否包含当前项
    !res.includes(item) && res.push(item);
  }
  return res;
}

var arr = [1, 0, 1, 2, 6, 3, 3, 2, 2, 8, 8, 4, 5, 6, 5, 4, 5, 7, 9, 0];
console.info(uniArray(arr).sort()); // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Map 去重

原理同 indexOf, 都是去循环然后去判断当前 item 值是否存在一个另外一个数据中, 只不过这里利用了 map 对象的特性

javascript
function uniArray(arr) {
  var res = [];
  var m = new Map();
  var item;
  for (var i = 0, len = arr.length; i < len; i++) {
    item = arr[i];
    if (!m.get(item)) {
      m.set(item, true);
      res.push(item);
    }
  }
  return res;
}

var arr = [1, 0, 1, 2, 6, 3, 3, 2, 2, 8, 8, 4, 5, 6, 5, 4, 5, 7, 9, 0];
console.info(uniArray(arr).sort()); // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

sort 去重1(会改变原数组元素的索引)

javascript
function uniArray(arr) {
  arr.sort();
  var res = [];
  var item, next;
  for (let i = 0, l = arr.length; i < l; i++) {
    // 数组已经排序了, 所以相同的值肯定在一起
    item = arr[i];
    next = arr[i + 1];
    item !== next && res.push(item);
  }
}

var arr = [1, 0, 1, 2, 6, 3, 3, 2, 2, 8, 8, 4, 5, 6, 5, 4, 5, 7, 9, 0];
console.info(uniArray(arr).sort()); // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

sort 去重2(会改变原数组元素的索引)

javascript
function uniArray(arr) {
  arr.sort();
  var res = [];
  var item, lastItem;
  for (let i = 0, l = arr.length; i < l; i++) {
    // 因为 arr 已经排序了, 所有相同的值都在一起
    // 每次都获取 res 中的最后一项去和 arr 遍历到的当前项比较
    // 如果你上一次 push 过了,这一次就不会 push
    item = arr[i];
    lastItem = res[res.length - 1];
    item !== lastItem && res.push(item);
  }
}

var arr = [1, 0, 1, 2, 6, 3, 3, 2, 2, 8, 8, 4, 5, 6, 5, 4, 5, 7, 9, 0];
console.info(uniArray(arr).sort()); // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

sort + reduce 去重(会改变原数组元素的索引)

原理同 sort去重2

javascript
function uniArray(arr) {
  var res = [];
  arr.sort().reduce((prev, item) => {
    item !== prev[prev.length - 1] && res.push(item);
    return prev;
  }, []);
  return res;
}

var arr = [1, 0, 1, 2, 6, 3, 3, 2, 2, 8, 8, 4, 5, 6, 5, 4, 5, 7, 9, 0];
console.info(uniArray(arr).sort()); // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Set 去重(常用)

利用了 set 对象无法添加重复的值的特性

javascript
function uniArray(arr) {
  return Array.from(new Set(arr));
}

var arr = [1, 0, 1, 2, 6, 3, 3, 2, 2, 8, 8, 4, 5, 6, 5, 4, 5, 7, 9, 0];
console.info(uniArray(arr).sort()); // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

利用对象做映射去重(常用)

原理和 Set 去重类似, 利用对象无法设置两个相同的 key 属性的特性

javascript
function uniArray(arr) {
  var res = [];
  var uniValues = {};
  var item;
  for (var i = 0, len = arr.length; i < len; i++) {
    item = arr[i];
    if (!uniValues[item]) {
      uniValues[item] = true;
      res.push(res);
    }
  }
  return res;
}

var arr = [1, 0, 1, 2, 6, 3, 3, 2, 2, 8, 8, 4, 5, 6, 5, 4, 5, 7, 9, 0];
console.info(uniArray(arr).sort()); // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

面试题: 将数组扁平化后去重并排序

Q: 写个方法,处理将下列多维数组:

  1. 将数组扁平化
  2. 数组去重
  3. 将数组排序

A: 解题思路:

  1. 将原数组复制一份到新的数组 stack
  2. 循环(扁平化数组 + 去重)
    1. shift弹出数组的第一项, 会改变原数组, 去判断, 然后进行对应的操作
      1. 如果是数组就进行扁平化操作
      2. 如果不是数组就保存结果并且进去去重操作
  3. 将最后获得的结果排序返回
javascript
// 扁平化数组 + 去重 + 排序
function flatUniqueSort(arr, sortCallback) {
  if (typeof sortCallback !== "function" && sortCallback !== undefined) {
    throw new TypeError("sort callback must be a function");
  }

  var stack = [].concat(arr);
  if (stack.length <= 1) {
    return stack;
  }

  // 判断是否是一个数组
  var isArray = function (val) {
    return Object.prototype.toString.call(val) === "[object Array]";
  };

  var res = []; // 存放扁平化和去重后的结果
  var uniMap = {};

  var item;
  while (stack.length) {
    // 每次循环弹出 stack 的第一项, 并且赋值给 item
    item = stack.shift();

    if (isArray(item)) {
      // 如果item是数组: 就进行扁平化操作
      // 直接将 item 这个数组 concat 到 statck,
      // 相当于: [[1, 2], 3] => [3, 1, 2]
      // 那后面再循环的时候, 就不会在进入这个 if 判断
      // 重复执行这个操作: 直到 stack 中的元素全部被弹出, 循环结束
      stack = stack.concat(item);
    } else {
      // 如果 item 不是数组: 就进行保存数据并去重的操作
      // 就判断当前的 item 的值在 uniMap 中是否已经存在了, uniMap 是个对象
      // 对象不能添加2个同样的key, 如果能获取到值, 证明 item 已经存在了
      // 如果 item 不存在, 就将 item 放到结果数组中, 并且在 uniMap 中记录当前 item 的值
      // 下一次循环, 如果 uniMap 中有这个值就不会进入这个 if 语句, 从而达到去重的目的
      if (!uniMap[item]) {
        uniMap[item] = true;
        res.push(item);
      }
    }
  }
 
  // 最后: 排序扁平化并且去重的结果数组, 也可以把这个步骤放到这个函数之外
  typeof sortCallback === "function" ? res.sort(sortCallback) : res.sort();
  return res;
}


var arr = [
  1,
  4,
  [1, 2, 3],
  [0, 3, 4, 5, 5],
  [[4, 7], 0, 6, [5], 7, 9, [5, 8, [1, 2, [5], 5, 6], 9, 1, 5], 3, 4],
  [6, 7, 8, [1, 2, 3, 4], 4, 5, 3],
  [6, 10, [3, 4, 6], 9, 8, [4, 3, 2, 1], 6, 4, 3],
  5,
  3,
];
var res = flatUniqueSort(arr, (a, b) => a - b);
console.info(res); // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

Released under the MIT License.