数组介绍
同大多数编程语言一样, 数组是多个数据的容器对象, 用于描述一系列有序数据的组合
创建数组
// 创建数组
const items = new Array();
const books = [];
// 创建指定长度的数组
const week = new Array(5);
遍历数组
// 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);
}
基础学习
声明数组
var arr = []
推荐, 使用数组字面量var arr = new Array()
通过系统内置的构造函数 Arrayvar arr = Array()
不使用, 虽然可以,
三种声明方式的数组都继承: Array.prototype
也就是说, 所有的数组实例对象都能使用 Array.prototype 上的所有方法, 同理: 对象类型也是这样的
Array 与 Object 类型的关系
Array 是特殊的 Object
对象底层也是用 obj[key]
来取值的, 数组取值就是利用这种机制
// 数组的每一个元素对应了一个下标(也叫索引), 下标从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
的数组就叫做 稀松数组
- 如果数组最后一位是
empty
那么,那么不会计算最后一位 - 如果使用构造函数来创建数组, 就不能有
empty
的元素, 因为 new Array, 传的是参数, 参数不能随便乱写, 会语法错误
var arr = [,1,2,,,3,4,]; // length: 7
console.log(arr);
Array 构造函数创建数组注意点
- 如果只有一个参数, 并且是一个
正整数
(比如:3
) , 它会直接创建一个长度为3, 元素全部是empty
的数组 - 如果只有一个参数, 这个数是一个number类型, 但是不是一个
正整数
就会报错, 比如Array(-5)
Array(1.2)
- 如果只有一个参数, 并且这个数不是number类型, 他会创建一个数组, 并且把这个参数作为数组的第一个元素
- 如果有多个参数, 他会直接把这些参数全部放到一个数组中, 然后返回
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)上的方法, 原因如下:
- 所有的数组实例对象都是有 Array 这个构造函数创建的
- 实例对象本身没有这些函数, 就会沿着原型链去查找
- 实例对象去找其构造函数的原型对象上的方法, 肯定是能找到的
添加元素: push/unshift
方法名 | 作用效果 | 返回值 | 是否改变原数组 |
---|---|---|---|
push | 在数组的最后面添加元素 | 新的 length | 是 |
unshift | 在数组的最前面添加元素 | 新的 length | 是 |
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 | 弹出数组第一个元素 | 被弹出的元素 | 是 |
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 翻转数组顺序
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)
: 数组排序, 返回排序后的数组, 会改变原数组
- 按照元素 ASCII 码, 十进制的值来排序
- 默认是升序排序(假设元素全部是number类型)
- 如果想指定倒序排序, 可以传入一个检测函数
- 这个函数有二个参数
a, b
- 必须 return 一个number类型的值
- 如果return的值是负数, a 就排在 b 前面
- 如果return的值是正数, b 就排在 a 前面
- 如果return的值是 0, 则顺序保持不懂
- 这个函数有二个参数
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?) :剪切或者替换数组现有的元素, 返回被剪切的值, 改变原数组
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 合并数组
合并两个数组, 并且返回合并后的新数组
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) 截取数组中某个位置的元素,并返回截取的元素
- start: 从哪个下标开始截取,如果是负数则从后往前截取, 如果没有
end
参数, 则截取到最后 - end: 截取到那个下标, 但是不包括这个下标
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 | 将字符按指定字符串切割为数组 | 数组 | 不改变 |
// 数组 -> 字符串
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']
类数组
什么是类数组
- 是一个类似于数组对象, key的是索引值, 并且有 length 属性
- 没有 Array 构造函数原型对象上的方法
arguments
对象就是一个典型的类数组对象Array-Like
// 以下就是一个类数组
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}
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
解题思路:
obj.push(1)
此时会执行obj[obj.length] = 1;
也就是覆盖obj[2]
的值, 将值修改为1
并且把length
修改为3
,因为数组的索引必须从0开始,如果前两个位置没有值就是 empty 元素obj.push(2)
此时会执行obj[obj.length] = 2
也就是会覆盖obj[3]
的值(因为执行第一步的时候已经把 length 修改为 3 了), 所以会将obj[3]
值修改为2
练习2: 数组的随机排序(打乱数组)
/**
* 数组随机排序(打乱数组)
* @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 方法
/**
* 手动实现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', '可以']
// 获取字符串的字节数
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: 数组去重
实现数组去重方法
/**
* 数组去重(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
/**
* 获取任意值的准确类型
* @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: 计算一个字符串中单个字符出现的次数
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;
}
数组方法
操作方法
函数名 | 返回值 | 作用 | 是否改变原数组 |
---|---|---|---|
slice | Array | 截取原数组中 n 个元素并返回一个新数组 | N |
concat | Array | 合并两个数组并返回一个新数组 | N |
splice | Array | 截取/增加 n 个数组元素 | Y |
pop | any | 删除数组最后一项并返回 | Y |
push | Number | 最素组末尾添加一项并返回数组改变后的 length | Y |
shift | any | 删除数组最后一项并返回 | Y |
unshift | Number | 在数组最前添加一项并返回改变后数组的 length | Y |
排序方法
函数名 | 返回值 | 作用 | 是否改变原数组 |
---|---|---|---|
sort | undefined | 对数组进行排序(默认升序) | Y |
reverse | Array | 翻转数组顺序 | N |
转换方法
函数名 | 返回值 | 作用 | 是否改变原数组 |
---|---|---|---|
join | String | 将数组通过指定符号拼接成字符串 | N |
迭代方法
函数名 | 返回值 | 作用 | 是否改变原数组 |
---|---|---|---|
some | Boolean | 对数组每一项都运行测试函数, 如果至少有 1 个元素符合要求,就返回 true,否则返回 false | N |
every | Boolean | 对数组每一项都运行测试函数, 如果全部符合要求就返回 true, 否则返回 false | N |
forEach | undefined | 对数组每一项都运行传入的函数,没有返回值 | N |
filter | Array | 对数组每一项都运行传入的函数,函数返回 true 的项会组成数组之后返回 | N |
map | Array | 对数组每一项都运行传入的函数,返回由每次函数调用的结果构成的数组 | N |
copyWithin(target, start, end)
从 start 位置开始(包含 start)复制到 end 位置结束(不包含 end), 然后将这个结果从 target 位置开始替换 不改变数组长度, 返回改变后的数组, 但是会改变原数组
- 复制元素: 从 start 索引开始到 end 结束, 但是不包含 end
- 替换元素: 从 target 开始, 复制的元素有多少个, 就向后替换多少个
// 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
/**
* 手动实现 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
返回数组的迭代器对象
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]
这个属性的对象
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 练习
排序一个二维数组中的每一个数组
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
, 这个方法会改变原数组
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
callback(item, index, array)
: 回调函数必须返回一个布尔值, 就算不返回布尔值, 内部也会隐式转换成布尔值item
当前遍历到的元素index
当前遍历到的索引array
指向调用find
方法的对象
thisArg
可选参数, 可以指定 callback 的 this 指向, 默认情况下: callback 中的 this 指向 window, 严格模式下指向undefined
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
var arr = ["a", , "c", , "e"];
arr.find((item) => {
console.log(item); // a undefined c undefined e
});
flat 多维数组转一维数组
将多维数组扁平化, 返回一个新的数组, 不会改变原数组
flat(deep)
: deep 必须是一个正整数, 而且必须大于 0 才会生效- 如果原数组是一个稀松数组, 返回的新数组, 会忽略稀松数组的 empty 元素
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
返回的结果数组, 放到一个新的数组中, 并且扁平化处理
- callback:
- item: 当前遍历的元素
- index: 当前遍历的索引
- array: 被遍历的原数组
- thisArg: 指定
callback
函数的时候 this 指向, 如果不传这个参数, 默认 this 指向 window,严格模式下是undefined
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
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)
将一个 类数组对象
或者 可迭代对象
转换成一个新的数组
- arrLike: 被转换的类数组对象
- mapFn: 转换的时候, 每一项都会被这个函数处理, 然后把这个函数处理后的值放到返回的数组小红
- item: 当前项的值
- index: 当前遍历到的索引值
- thisArg: 指定 mapFn 执行时的 this 指向
// 类数组--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 多数组去重
// 多数组去重
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 源码实现
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
判断一个数组中是否包含某个值, 返回一个布尔值
- searchItem: 需要搜索的值
- formIndex: 从那个位置开始搜索, 如果这个值大于数组的 length 则返回 false
var arr = ["a", "b", "hello", "word"];
var searchItem = "hello";
if (arr.includes(arr)) {
console.log("数组中包含" + searchItem);
} else {
console.log("数组中不包含" + searchItem);
}
数组方法重写
原生就要的方法为什么要重写呢? ?? 重写只是为了更加理解原生方法的运行原理, 有助于学习和记忆
forEach
/**
* 遍历数组
* @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
/**
* 筛选数组(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
/**
* 遍历数组, 并且返回新的数组, 数组的元素是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
/**
* 检测数组所有项是否符合需求
* @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
/**
* 返回检测函数要求的第一个元素,没有找到返回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
/**
* 数组扁平化
* @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
/**
* 数组是否包含指定的值
* @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
字符串数组互相装换
/**
* 将数组按照指定字符分割为字符串
* @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
返回一个迭代器对象
/**
* 获取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
/**
* 合并两个数组并返回新的数组
* @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
/**
* 将数组倒转顺序(虽然倒着遍历一遍是最简单的, 但是那样性能不如只遍历一半, 而且不会改变原数组)
* @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
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
/**
* 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
/**
* 向数组的最后添加元素
* @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
/**
* 弹出数组最后一个元素并且返回
* @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
删除/替换原数组, 也就是说会改变原数组
/**
* 完美实现 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
/**
* 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 循环去重
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 去重
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 去重
// 推荐这种, 语法更明确, 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 是否存在一个数组中
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 对象的特性
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(会改变原数组元素的索引)
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(会改变原数组元素的索引)
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
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 对象无法添加重复的值的特性
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 属性的特性
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: 写个方法,处理将下列多维数组:
- 将数组扁平化
- 数组去重
- 将数组排序
A: 解题思路:
- 将原数组复制一份到新的数组
stack
- 循环(扁平化数组 + 去重)
- shift弹出数组的第一项, 会改变原数组, 去判断, 然后进行对应的操作
- 如果是数组就进行扁平化操作
- 如果不是数组就保存结果并且进去去重操作
- shift弹出数组的第一项, 会改变原数组, 去判断, 然后进行对应的操作
- 将最后获得的结果排序返回
// 扁平化数组 + 去重 + 排序
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]