返回博客

发布于  2024 年 2 月 26 日,星期一

手撕代码系列(二)

"手撕代码系列(二)"这个标题暗示了博客内容将深入探讨前端开发中的代码实现细节。通过"手撕"这一形象的表达,作者强调了对代码的深入理解和手动实现的重要性,而非仅仅依赖框架或库。这种做法有助于提升开发者的编程技能和对底层原理的掌握。"系列(二)"表明这是一个持续更新的系列文章,每篇都聚焦于不同的代码实现或技术难点。整体而言,这个标题反映了博客内容将注重实践和深度,旨在帮助读者通过手动编写代码来加深对前端技术的理解。

前言

系列首发于公众号『沉浸式趣谈』https://mp.weixin.qq.com/s?__biz=MzkyOTI2MzE0MQ==&mid=2247485576&idx=1&sn=5ddfe93f427f05f5d126dead859d0dc8&chksm=c20d73c2f57afad4bbea380dfa1bcc15367a4cc06bf5dd0603100e8bd7bb317009fa65442cdb&token=1071012447&lang=zh_CN#rd ,若不想错过更多精彩内容,请“星标”一下,敬请关注公众号最新消息。

手撕代码系列(二)

手写函数柯里化 curring

/**
 * 函数柯里化 curring
 * @param {Function}
 * @return 视具体方法而定
 *
 * @logic
 *  1.创建一个参数数组 args
 *  2.创建一个函数,接收参数列表
 *  3.函数判断参数数组是否有值
 *  4.如果有值,则往 args 中进行 push 操作,否则就是没有参数了,则直接进行调用
 *  5.调用后,将 args 参数数组清空
 *  6.返回参数数组
 */const curring = fn => {    let args = [];    return function temp(...rest) {        if (rest.length) {            args.push(...rest);            return temp;        } else {            let result = fn.apply(this, args);            args = [];            return result;        }    }}// test:const add = (...args) => {    return args.reduce((prev, next) => prev + next, 0);}console.log(curring(add)(1)(2)(3)(4)(5)());  // 15console.log(curring(add)(1)(2)(3)(4,5)()); // 15console.log(curring(add)(1,2)(3,4,5)()); // 15console.log(curring(add)(1)(2,3,4,5)()); // 15
复制代码

手写数组乱序方法 shuffle

/**
 * 随机洗牌
 * @param {Array} arr 要被打乱的数组
 * @returns 打乱后的元素
 *
 * @logic
 *  1.  从还没处理的数组中,从 [0, array.length] 之间取一个随机数 random number
 *  2.  从剩下的 array.length 中把第 random 元素取出来放在新数组 result 中
 *  3.  删除原数组中第 random 个元素
 *  4.  重复2,3步骤直到所有元素取完
 *  5.  最终返回新数组即可
 */const shuffle = source => {    let arr = Array.from(source);    let result = [];    while (arr.length) {        let random = (Math.random() * arr.length) | 0;        // let random = Math.floor(Math.random() * arr.length);        // let random = (Math.random() * arr.length) >> 0;        // let random = (Math.random() * arr.length) << 0;        // let random = (Math.random() * arr.length) >>> 0;        // let random = ~~(Math.random() * arr.length);        result.push(arr[random]);        arr.splice(random, 1)    }    return result;}// test:let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20];console.log("shuffle(numbers) ------->", shuffle(numbers));// shuffle(numbers) -------> [//    9,  1, 17,  6, 10, 12, 18,//   19, 20,  8, 13,  2, 11, 15,//    7,  4, 16, 14,  5,  3// ]
复制代码

手写解析 URL Params 方法 getUrlParams

/**
 * 解析 URL Params getUrlParams
 * @param {String} urlStr
 * @return {Object} 参数对象
 */// 方法一:const getUrlParams = url => {    // 解析url,获取参数部分    let urlStr = url.split('?')[1];    // 判断是否有 hash 值    let newStr = '';    // 去重 hash 值逻辑    if (urlStr.includes('#')) {        newStr = urlStr.split('#')[0];    }    // 参数重置化    let res = new URLSearchParams(newStr);    // Object.fromEntries() 方法将 key value 数组形式转换为对象;    /**
    demo:
        let obj = {
            a: 1,
            b: 2
        }
        console.log(Object.fromEntries(Object.entries(obj)));
     */    return Object.fromEntries(res.entries())}// 方法二:const getUrlParams = url => {    let urlParams = new URL(url);    return Object.fromEntries(urlParams.searchParams.entries());}// test:let tempUrl =    "https://www.baidu.com:80/temp/src/index.html?name=John&sex=14#hash=title";console.log("getUrlParams(tempUrl) ------->", getUrlParams(tempUrl));// getUrlParams(tempUrl) -------> { name: 'John', sex: '14' }
复制代码

手写数组扁平化 myFlat

/**
 * 数组扁平化 myFlat
 * @param {Array} source 需要扁平化的数组
 * @returns 扁平化后的新数组
 */// 方法一:const myFlat = source => {    // 结果数组    let result = [];    // 遍历    for (let i = 0; i < source.length; i++){        // 数组元素        let item = source[i];        // 判断当前项是否为数组,若是数组递归执行        if (Array.isArray(item)) {            // result.concat(myFlat(item)) 使用之前的列表(result)拼接当前的列表(item),因为 concat 不会修改原数组,重新赋值给 result 数组            result = result.concat(myFlat(item));        } else {            // 不是数组,则添加到 result 数组中            result.push(item);        }    }    // 返回结果    return result;}// 方法二:// some + 扩展运算符:通过 while 循环来持续判断当前项是否为数组,如果为数组,直接修改原数组 arr 等于 空数组展开当前数组,按此逻辑继续执行。// some:如果有一个满足条件,则返回 true, 后续将和 includes 和 indexof 方法一样,不会再进行后续检测,如果不满足则返回 false// any: 数组中的所有元素都满足条件,返回 boolean 值。const MyFlat = (arr) => {    while (arr.some((item) => Array.isArray(item))) {        console.log(arr);        arr = [].concat(...arr);    }    return arr;};// 方法三:// split + toString(): 此方法弊端,如果数组中的元素都是数字,那经过此方法会直接转为字符串。const MyFlat = (arr) => {    return arr.toString().split(",");};// 方法四:// ES6 flatconst MyFlat = (arr) => {    return arr.flat(Infinity);};// test:let arr = [1, [2, [3, 4, 5]]];console.log("myFlat(arr) after------>", myFlat(arr)); // myFlat(arr) after------> [ 1, 2, 3, 4, 5 ]
复制代码

手写 myInstanceof

/**
 * instanceof
 * instanceof 运算符用户检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。
 * 语法:object(实例对象) instanceof constructor(构造函数,与 prototype 是成对出现的,你指向我,我指向你)
 * @param {Object} object 某个实例对象
 * @param {Function} constructorFn 某个构造函数
 * @return {Boolean}
 */const myInstanceof = (object, constructorFn) => {    // Object.getPrototypeOf(object) === object.__proto__; 都是用来获取某个对象的隐式原型    let objectRes = Object.getPrototypeOf(object);    // prototype:显式原型    const constructorRes = constructorFn.prototype;    while (true) {        if (!objectRes) return false;        // 如果构造函数的 prototype 在对象的实例中,直接返回 true        if (objectRes === constructorRes) return true;        // 继续向下寻找        objectRes = Object.getPrototypeOf(objectRes);    }};// test:function F() { };function D() { };let f = new F();// 表示: 构造函数 F 是否出现在 f 对象实例上console.log(myInstanceof(f, F)); // true// 表示: 构造函数 D 是否出现在 f 对象实例上console.log(myInstanceof(f, D)); // false
复制代码

特殊字符描述:

  1. 问题标注 Q:(question)
  2. 答案标注 R:(result)
  3. 注意事项标准:A:(attention matters)
  4. 详情描述标注:D:(detail info)
  5. 总结标注:S:(summary)
  6. 分析标注:Ana:(analysis)
  7. 提示标注:T:(tips)

往期推荐:

最后:

# JavaScript# 算法# 数据结构# 代码质量# 设计模式