前端学到现在,发现有些代码平时看了千八百遍,都快要看吐了的代码关键时刻上居然写不出来了,这真的是有点难受啊。所以就在这里做一些总结,后面忘了可以及时复习。 本系列大致分为四篇:
- 第一篇 call、apply、bind、new、柯里化、函数组合、防抖、节流
- 第二篇 数组去重、数组扁平化、千分位、深拷贝、promise实现
- 第三篇 常用排序算法(正在努力中)
- 第四篇 大厂面试中经常出现的算法题(正在努力中)
1. 数组去重
数组去重的方法有很多,常见的有以下五种。数组去重参考链接
// 1. 利用Set结构去重
function uniqueSet(arr) {
if (!Array.isArray(arr)) {
throw Error('arr必须是一个数组')
}
return Array.from(new Set(arr));
};
// 2. 创建一个新数组,使用indexOf或者includes判断是否存在的方式去重
function uniqueExist(arr) {
if (!Array.isArray(arr)) {
throw Error('arr必须是一个数组')
}
const result = [];
arr.forEach(item => {
// result.indexOf(item) === -1
!result.includes(item) && result.push(item);
});
return result;
};
// 3. 利用对象的key不能重复的特性去重/利用Map结构key不能重复的特性去重
function uniqueKey(arr) {
if (!Array.isArray(arr)) {
throw Error('arr必须是一个数组')
}
const obj = {};
const result = [];
arr.forEach(item => {
if (!obj[item]) {
obj[item] = 1;
result.push(item);
} else {
obj[item]++;
}
})
return result;
}
// 4. 对数组排序后使用indexOf判断索引的方式去重
function uniqueSort(arr) {
if (!Array.isArray(arr)) {
throw Error('arr必须是一个数组')
}
// 注意sort方法会改变原数组,用concat生成一个新的数组
const sortArr = arr.concat().sort();
const result = [];
sortArr.forEach((item, index) => {
sortArr.indexOf(item) === index && result.push(item);
})
return result;
}
// 5. 利用双重循环+splice方法去重
function uniqueSplice(arr){
if (!Array.isArray(arr)) {
throw Error('arr必须是一个数组')
}
let len = arr.length;
for(let i = 0; i < len - 1; i++){
for(let j = i+1; j < len; j++){
// 双等号会触发false == 0为true
if(arr[i] === arr[j]){
arr.splice(j,1);
len--;
j--;
}
}
}
return arr;
}
// 测试
var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}];
console.log(uniqueSet(arr));
console.log(uniqueExist(arr));
console.log(uniqueKey(arr));
console.log(uniqueSort(arr));
console.log(uniqueSplice(arr));
// [1, 'true', true, 15, false, undefined, null, NaN, 'NaN', 0, 'a', {}, {}] length = 13
// [1, 'true', true, 15, false, undefined, null, NaN, 'NaN', 0, 'a', {}, {}] length = 13
// [1, 'true', 15, false, undefined, null, NaN, 0, 'a', {}] length = 10
// [0, 1, 15, 'NaN', {}, {}, 'a', false, null, 'true', true, undefined] length = 12
// [1, 'true', true, 15, false, undefined, null, NaN, NaN, 'NaN', 0, 'a', {}, {}] length = 14
2. 数组扁平化
// 1. flat方法实现
function myFlat(arr) {
return arr.flat(Infinity);
}
// 2. 序列化实现
function myFlat(arr) {
if (!Array.isArray(arr)) {
throw Error('arr必须是一个数组');
};
const str = JSON.stringify(arr);
// 把[]干掉,保留数字和逗号
const flatArr = `[${str.replace(/\[|\]|/g, '')}]`;
return JSON.parse(flatArr);
}
// 3. 递归实现
function myFlat(arr) {
if (!Array.isArray(arr)) {
throw Error('arr必须是一个数组');
};
const result = [];
arr.forEach(item => {
if (item instanceof Array) {
result.concat(myFlat(item));
} else {
result.push(item);
}
})
return result;
}
// 4. 数组转换实现
function myFlat(arr) {
if (!Array.isArray(arr)) {
throw Error('arr必须是一个数组');
};
// console.log('test', [1, [1,2], [1,2,3]].toString(), [1, [1,2], [1,2,3]].join());
// test 1,1,2,1,2,3 1,1,2,1,2,3
// return arr.toString().split(',').map(Number);
return arr.join().split(',').map(Number);
}
// 5. reduce实现(类似递归)
function myFlat(arr) {
if (!Array.isArray(arr)) {
throw Error('arr必须是一个数组');
};
return arr.reduce((sum, item) => {
const flatArr = item instanceof Array ? myFlat(item) : [item];
return sum.concat(flatArr);
}, []);
}
// 6. 展开运算符实现
function myFlat(arr) {
if (!Array.isArray(arr)) {
throw Error('arr必须是一个数组');
};
while(arr.some(Array.isArray)) {
arr = [].concat(...arr);
}
return arr;
}
3. 千分位
// 1. toLocaleString实现
function thousands(num) {
if (isNaN(num)) return num;
// toLocaleString只针对Number对象有效,虽然String原型上也挂载了该方法,但并不会进行千分位的格式化
return Number(num).toLocaleString();
}
// console.log('123456789123456'.toLocaleString()); 123456789123456
// console.log(123456789123456.toLocaleString()); Uncaught SyntaxError: Invalid or unexpected token
// console.log(Number(123456789123456).toLocaleString()); 123,456,789,123,456
// 2. 正则表达式实现
function thousands(num) {
if (isNaN(num)) return num;
// ?= 是零宽正预测先行断言,标记了字符出现位置的右边必须是什么表达式,[查看更多](https://www.cnblogs.com/chenmeng0818/p/6370819.html)
// 正则表达式分组
const reg = /\d{1,3}(?=(\d{3})+$)/g;
// $&是replace的用法,[查看更多](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/String/replace)
return String(num).replace(reg, '$&,');
}
// 使用match方法可以清晰的看到使用小括号是如何分组的
// console.log('123456789123456'.match(reg))
// ["123", "456", "789", "123"]
// 3. for循环实现
function thousands(num) {
if (isNaN(num)) return num;
const str = String(num);
const arr = [];
const len = str.length;
// 从前往后数
for (let i = 0; i < len; i++) {
// 从后往前塞
i % 3 === 0 && arr.unshift(str.substring(len - i - 3, len - i))
}
return arr.join(',')
}
// 4. reduce实现
function thousands(num) {
if (isNaN(num)) return num;
const arr = String(num).split('').reverse();
return arr.reduce(function(sum, item, index) {
const prefix = index % 3 === 0 ? ',' : '';
return item + prefix + sum;
})
}
4. 深拷贝
function deepClone(obj) {
if(obj instanceof RegExp) return new RegExp(obj);
if(obj instanceof Date) return new Date(obj);
if(obj === null || typeof obj !== 'object') return obj;
// 找到当前对象的原型,并创建一个新的对象,确保不丢失父级原型上的方法
let t = new obj.constructor();
// 使用for in 会无法获取到不可枚举的属性和Symbol属性
// for(let key in obj) {
// t[key] = deepClone(obj[key]);
// }
// 不可枚举属性可使用Object.getOwnPropertyNames,Symbol属性可使用Object.getOwnPropertySymbols
// Reflect.ownKeys则是上述两者的和
Reflect.ownKeys(obj).forEach(function(key) {
t[key] = deepClone(obj[key]);
})
return t;
}
5. Promise
- then 待完善
- catch 待完善
- resolve 待完善
- all
function promiseAll(arr) {
const len = arr.length;
let index = 0;
// fill方法 填充的类型如果为对象,那么被赋值的是同一个内存地址的对象,而不是深拷贝对象
let result = new Array(len).fill(undefined);
return new Promise((resolve, reject) => {
for(let i = 0; i < len; i++) {
Promise.resolve(arr[i]).then(res => {
result[i] = res;
index++;
// 等待所有状态都扭转以后才返回结果
if(index === len){
resolve(result)
}
}).catch(err => reject(err));
}
});
}
var p1 = () => new Promise((resolve, reject) => {setTimeout(function(){resolve(1000)}, 1000)});
var p2 = () => new Promise((resolve, reject) => {setTimeout(function(){resolve(2000)}, 2000)});
var p3 = () => new Promise((resolve, reject) => {setTimeout(function(){resolve(3000)}, 3000)});
promiseAll([p1(), p2(), p3()]).then(res => console.log(res)).catch(err => console.log(err));
- race
function promiseRace(arr) {
return new Promise((resolve, reject) => {
for(let i = 0; i < arr.length; i++) {
// 任意一个状态扭转之后就返回结果
Promise.resolve(arr[i]).then(res => resolve(res)).catch(err => reject(err));
};
});
};
var p1 = () => new Promise((resolve, reject) => {setTimeout(function(){resolve(1000)}, 1000)});
var p2 = () => new Promise((resolve, reject) => {setTimeout(function(){resolve(2000)}, 2000)});
var p3 = () => new Promise((resolve, reject) => {setTimeout(function(){resolve(3000)}, 3000)});
promiseRace([p1(), p2(), p3()]).then(res => console.log(res)).catch(err => console.log(err));