前端基础之手撕代码(二)

498 阅读4分钟

前端学到现在,发现有些代码平时看了千八百遍,都快要看吐了的代码关键时刻上居然写不出来了,这真的是有点难受啊。所以就在这里做一些总结,后面忘了可以及时复习。 本系列大致分为四篇:

  1. 第一篇 call、apply、bind、new、柯里化、函数组合、防抖、节流
  2. 第二篇 数组去重、数组扁平化、千分位、深拷贝、promise实现
  3. 第三篇 常用排序算法(正在努力中)
  4. 第四篇 大厂面试中经常出现的算法题(正在努力中)

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

  1. then 待完善
  2. catch 待完善
  3. resolve 待完善
  4. 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));
  1. 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));