JS工具函数封装(持续更新)

550 阅读11分钟

记录一些平时工作中或者练习算法时写的函数

1. 获取url参数

function getSearch(name, search) {
  if (!search && typeof window !== 'undefined') {
    search = window?.location?.search
  }
  if (!search || typeof search !== 'string') return null
  search = search.startsWith('?') ? search.slice(1) : search

  const reg = new RegExp(`(^|&)${name}=([^&]*)(&|$)`, 'g')

  const match = search.match(reg)?.map((item) => item.replaceAll('&', ''))
  if (!match) {
    return null
  }
  if (match.length == 1) {
    return decodeURIComponent(match[0].split('=')[1])
  }
  return match.map((item) => decodeURIComponent(item.split('=')[1]))
}

function getSearch2(search,name) {
  if (!search && typeof window !== 'undefined') {
    search = window?.location?.search
  }
  if (!search || typeof search !== 'string') return null
  search = search.startsWith('?') ? search.slice(1) : search
  let paramsObj = search
    .split('&')
    ?.map((item) => item.split('='))
    ?.reduce((acc, curr) => {
      // 将 ?q=1&q=2 转换成 q:[1,2]
      if (acc[curr[0]]) {
        // 如果是数组
        if (Array.isArray(acc[curr[0]])) {
          acc[curr[0]].push(curr[1])
        } else {
          let one = acc[curr[0]]
          acc[curr[0]] = [one, decodeURIComponent(curr[1])]
        }
      } else {
        acc[curr[0]] = decodeURIComponent(curr[1])
      }
      return acc
    }, {})

  return name ? paramsObj[name] : paramsObj
}

2. 扁平化数组转tree

/** 
 * @Description:
 * @param {Array} arr 目标扁平化数组
 * @param {String} pidFiled  表示父节点的字段名
 * @param {any} pidValue  表示父节点的值(基础数据类型)
 * @param {String} childFiled  表示子节点的字段名
 * @return {Array} 树形 数组
 * @author _Autumn_
 */
 
 //实际情况下,后端通常返回的字段名都是pid 和 id ,所以我们可以设置一下默认值(或者直接函数内部写死,看自己习惯,以及需求吧)
function array2Tree(arr, pidFiled = 'pid', pidValue = 0, childFiled = 'id') {
  return arr.filter((item) => {
    item.children = arr.filter((curr) => {
      return curr[pidFiled] == item[childFiled];
    });
    return item[pidFiled] == pidValue;
  });
}

3. 数组去重

1. 利用Set对象去重
function doAwayBySet(arr){
    return [...new Set(arr)]
}


2. 利用indexOf去重
function doAwayByArrMethod(arr){
    return arr.filter((item,idx) => arr.indexOf(item) == idx)
}

//对象数组去重(可指定字段,支持多字段,O(n))

function doAwayRepeatByFields(target, ...fields) {
  let tempArr = []
  let tempSet = new Set()
  for (let i = 0, len = target.length; i < len; i++) {
    // 生成键名
    const fieldsStr = fields.reduce((acc, curr) => {
      return (acc += target[i][curr])
    }, '')
    if (tempSet.has(fieldsStr)) {
      continue
    } else {
      tempSet.add(fieldsStr)
      tempArr.push(target[i])
    }
  }
  return tempArr
}

4. 生成指定范围的随机整数

function createRandomNum(min, max) {
  return Math.floor(Math.random() * (max - min + 1)) + min;
}

5. 生成rgba随机颜色

//依赖于 4 的函数
function setRandomColor(opacity = 1) {
  return `rgba(${createRandomNum(0, 255)},${createRandomNum(
    0,
    255
  )},${createRandomNum(0, 255)},${opacity})`;
}

6. 时间格式化

//补零
function padLeft(num) {
  return String(num).length > 1 ? String(num) : '0' + num;
}

//时间格式化
function formatDate(date = new Date(), format = 'YYYY-MM-DD hh:mm:ss') {
  date = date ? new Date(date) : new Date()
  let regMap = {
    Y: date.getFullYear(),
    M: padLeft(date.getMonth() + 1),
    D: padLeft(date.getDate()),
    h: padLeft(date.getHours()),
    m: padLeft(date.getMinutes()),
    s: padLeft(date.getSeconds())
  }
  return Object.entries(regMap).reduce((acc, [key, value]) => {
    return acc.replace(new RegExp(`${key}+`, 'g'), value)
  }, format)
}

7. 限制数字的最大最小值

function getLimitedNum(val, min, max) {
  let num = Math.max(min, val);
  num = Math.min(max, val);
  return num;
};

8. setTimeout 模拟 setInterval

function myInterval(fn, time, endC) {
  let timer;
  return {
    clearTimer() {
      if (timer) clearTimeout(timer);
    },
    setTimer() {
      if (timer) this.clearTimer();
      if (endC && typeof endC === "function") {
        if (endC(...arguments)) return;
      }
      timer = setTimeout(() => {
        fn.apply(this, arguments);
        this.setTimer.apply(this, arguments);
      }, time);
    },
  };
}

9. 深拷贝

deepClone(o) {
  if (Array.isArray(o)) {
    return o.map((item) => deepClone(item));
  }
  
  if (Object.prototype.toString.call(o) == "[object Object]") {
    let obj = {};
    for (let key in o) {
      obj[key] = deepClone(o[key]);
    }
    return obj;
  }
  
  return o;
}

10.根据某字段大小进行排序

//由于sort 方法 会直接改变原数组,所以这里利用 9 的 深拷贝函数,如果想直接使用原数组的化,就没必要做深拷贝,返回值也不需要

/** 
 * @Description:
 * @param {Array} target 要排序的数组
 * @param {String} filed  要用来排序的字段
 * @param {Boolean} order  (默认升序)true 代表 降序 ,false(可不传,因为undefined是个假值) 代表升序
 * @return {Array} 排序后的数组
 * @author _Autumn_
 */
function sortByFiled(target, filed, order) {
  let arr = deepClone(target);
  if (order) {
    arr.sort((a, b) => b[filed] - a[filed]);
  } else {
    arr.sort((a, b) => a[filed] - b[filed]);
  }
  return arr;
}

11.判断字符串是否是回文字符串

这里写了两种方法,第一种方法效率比第二种方法低


function isPalindrome1(str){
  return str.split("").reverse().join("") == str;  
}

function isPalindrome2(str) {
  let end = str.length;
  if (end < 2) return true;
  let start = 0;
  end--;
  while (start < end) {
    if (str[start] !== str[end]) return false;
    start++;
    end--;
  }
  return true;
}


12.最长回文字符串

//最长回文字符串
function longestPalindrome(str) {
    let end = str.length;
    if (str.length == 0 || str.length == 1) return str;

    let ret = "";
    //记录当前已有的子回文字符串的长度
    let count = 0;
    while (end >= 2) {
        for (let i = 0; i < end; i++) {
            //这一段代码如果是使用的 isPalindrome1 必须要加上,否则代码运行效率很低,leetcode那道题(最长回文字符串)我不加这段代码总是超时,如果使用 isPalindrome2 ,它本身算法就做了限制,效率高,无需这段代码,当然也可以加上
            if (str[i] !== str[end - 1]) continue;
            let nStr = str.slice(i, end);
            let len = nStr.length;
            if (len <= count) break;
            if (isPalindrome2(nStr)) {
                count = len;
                ret = nStr;
                break;
            }
        }
        end--;
    }
    return ret;

13. 判断一个数据是否是数组

function isArray(arg) {
  if (!Array.isArray) {
    return Object.prototype.toString.call(arg) === "[object Array]";
  }
  return Array.isArray(arg);
}

14. 获取数据的真实数据类型

function getType(val) {
  return Object.toString
    .call(val)
    .slice(8, -1)
    .toLowerCase();
}

15. 判断两个数据是否相等,对象进行递归判断

//实际上这个函数和深拷贝函数都有一个问题,就是对于非(数组和常规object类型) 的对象其实仍然是对地址值的比较

function isSame(o1, o2) {
  //先获取数据类型
  let type1 = Object.prototype.toString.call(o1);
  let type2 = Object.prototype.toString.call(o2);
  if (type1 !== type2) {
    return false;
  }
  //如果是数组先比较length
  if (Array.isArray(o1)) {
    let len1 = o1.length;
    let len2 = o2.length;
    if (len1 !== len2) {
      return false;
    }
    return o1.every((item, idx) => isSame(item, o2[idx]));
  }

  if (type1 === "[object Object]") {
    if (Object.keys(o1).length !== Object.keys(o2).length) {
      return false;
    }
    for (let key in o1) {
      if (!o2.hasOwnProperty(key)) {
        console.log(key, o2);
        return false;
      }

      if (!isSame(o1[key], o2[key])) {
        return false;
      }
    }
    return true;
  }
  return o1 === o2;
}


16. 千分位

function NumFormat(str) {
  //如果传进来的是个数值,转成String
  if (typeof str === "number" && !isNaN(str)) {
    str = String(str);
  }
  const reg = /(?!^)(?=(\d{3})+$)/g;

  return str.replace(reg, ",");
}

17. 对树结构数组将有子节点的数据排列在无子节点的兄弟节点的前方

//这个函数可以用于前端树结构展示时,让有子节点的数据展示在树结构前方,没有子节点的数据展示在后面(前提是数据量不大,没有使用懒加载时)

//判断一个数据是否是长度大于0的数组
function hasChildren(item) {
  return Array.isArray(item.children) && item.children.length ;
}
//排序
function sortByChildren(arr) {
  let hasChildrenArr = arr.filter((item) => hasChildren(item));
  let noChildrenArr = arr.filter((item) => !hasChildren(item));
  let concatArr = hasChildrenArr.concat(noChildrenArr);

  return concatArr.map((item) => {
    item.children =
      (hasChildren(item) && sortByChildren(item.children)) || [];
    return item;
  });
}

18. 模拟数组map方法

// 1. 首先要知道map方法的入参: 一个回调函数,参数为 (数组的每一项,每一项的索引,数组本身)
// 2. 其次要知道map方法的返参: 一个新的数组,数组的每一项元素就是入参回调函数的返回值

// 由于是原型链上的方法,所以我们使用Array.prototype ; 当然也可以把数组当作参数传入,不适用原型链

Array.prototype.myMap = function(callback){
    let retArr = []
    for(let i = 0,len = this.length;i < len;i++){
       retArr.push(callback && callback(this[i],i,this)) 
    }
    return retArr
}

19. 发布订阅者模式

//这样的写法存在一个问题,就是如果同名的once监听和on监听,once监听执行顺序是优先于on监听的,这两个监听没有办法按照插入顺序执行

  class Publisher {
        //基础功能
        /* 
    1. 发布者有一个发布事件器
    2. 每一个发布事件器都是一个数组,存取的是订阅者想要触发的事件
    
    3. 只触发一次就会销毁的方法
    4. 移除所有事件器中函数的方法
    5. 只移指定事件器中指定函数的方法
    6. 给事件器添加函数的方法
    7. 添加事件和函数的方法
    8. 触发事件中函数的方法

  */
        constructor() {
          this.subscribers = {} //订阅者列表,可是应该有订阅事件名,所以应该是一个对象
          this.onceCallBacks = {} //监听一次的对象
        }
        //只触发一次的事件
        once() {
          //只监听一次,一旦被触发之后就会立刻被销毁
          let eventName = Array.prototype.shift.call(arguments)
          if (!this.onceCallBacks[eventName]) {
            this.onceCallBacks[eventName] = []
          }
          for (let i = 0, cb; (cb = arguments[i++]); ) {
            if (typeof cb !== 'function') {
              continue
            }
            this.onceCallBacks[eventName].push(cb)
          }
        }
        //可多次触发的事件
        on() {
          //监听事件
          let eventName = Array.prototype.shift.call(arguments)
          if (!this.subscribers[eventName]) {
            this.subscribers[eventName] = []
          }
          for (let i = 0, cb; (cb = arguments[i++]); ) {
            if (typeof cb !== 'function') {
              continue
            }
            this.subscribers[eventName].push(cb)
          }
        }
        //抽取的事件触发函数
        emitEvent(arr, isOnce, args) {
          if (!arr) {
            return false
          }
          for (let i = 0, len = arr.length; i < len; i++) {
            let cb = arr[i]
            cb.apply(this, args)
          }
          if (isOnce) {
            arr.length = 0
          }
        }
        //事件触发
        emit() {
          //事件触发
          let eventName = Array.prototype.shift.call(arguments)
          let listener = this.subscribers[eventName],
            onceListener = this.onceCallBacks[eventName]
          if (this.isArrEmpty(listener) && this.isArrEmpty(onceListener)) {
            return null
          }
          //触发一次性事件
          this.emitEvent(onceListener, true, arguments)
          //触发非一次性事件

          this.emitEvent(listener, '', arguments)
        }
        //判断一个值是否为数组,如果是,数组长度是否存在(是否有元素)
        isArrEmpty(arr) {
          return !(Array.isArray(arr) && arr.length)
        }
        //抽取的公用取消事件函数
        offEvent(arr, cb) {
          if (!arr) {
            return false
          }
          //由于要删除元素,所以需要从后往前遍历
          for (let i = arr.length - 1; i >= 0; i--) {
            if (arr[i] !== cb) {
              continue
            }
            arr.splice(i, 1)
          }
        }
        //取消事件
        off(eventName, cb, isOnce) {
          //移除事件
          let listener = this.subscribers[eventName],
            onceListener = this.onceCallBacks[eventName]
          if (this.isArrEmpty(listener) && this.isArrEmpty(onceListener)) return false
          if (!cb) {
            //删除所有事件
            !this.isArrEmpty(onceListener) && (onceListener.length = 0)
            if (!isOnce) {
              listener.length = 0
            }
            return true
          }
          this.offEvent(onceListener, cb)
          if (!isOnce) {
            this.offEvent(listener, cb)
          }
          return true
        }
      }

20. 对一组数据进行求和(数字的求和,字符串的拼接)

    function getSum(...args){
       return args.reduce((acc,curr) => acc += curr)
    }

21. 求一组数值类型数据的平均值

    function getAverage(...args){
        return args.reduce((acc,curr) => acc += curr) / args.length
    }

22. 实现一个自己的call方法

首先要分析call方法他做了哪些事,以及入参和出参

  1. 作用: 使用特定的上下文环境(引用,其实就是改变一下函数调用的this),来执行函数,并返回该函数执行后的结果
  2. 入参: 一个context对象(用于修改函数调用的上下文环境); 以及后面的函数的入参(这里需要注意一下,如果context参数为null 或者 undefined 实际上,如果在非严格模式下,其实会有隐式的this声明,所以如果为这两个值,我们可以不用改变this指向)
  3. 返参: 函数调用的返回值

Function.prototype.myCall = function (context, ...args) {
// xx != null 可以只排除undefined 和 null 两个值
  if (context != null) {
    context[fn] = this;
    let res = context[fn](...args);
    delete context[fn];
    return res;
  }
  //如果不为null 和 undefined 
  //非严格模式下会有隐式this绑定;严格模式下会抛出TypeError错误,因为此时this 为 undefined了
  return this(...args);
};


23.节流函数

这里暂时只使用时间戳来实现一个节流函数

/**
 * 节流函数
 * 1. 是什么
 *    节流函数是一种规定最小频率执行的函数,也就是说,如果函数执行之后,没有到达时间间隔,再调用函数时就不会再次执行这个函数
 * 2. 实现方式
 *    1. 时间戳
 *    2. 定时器
 * 3. 用法
 *    一般用于滚动事件,鼠标移动等会持续触发的事件函数中
 *
 */

//1. 时间戳

function throttle(fn, wait = 500, immediate = false) {
  //获取当前时间戳
  let start = Date.now();
  let args = Array.prototype.slice.call(arguments, 3);
  if (immediate) {
    typeof fn == "function" && fn.apply(null, args);
    start = Date.now();
  }
  return function () {
    let end = Date.now();
    if (end - start >= wait) {
      typeof fn == "function" && fn.apply(null, arguments);
      //执行完成之后要修改start时间,用其作为下一次函数执行的起始时间
      start = Date.now();
    }
  };
}

24. 防抖函数

/**
 * 防抖
 * 1. 是什么:
 *    给函数设置一个执行周期,在这个时间周期内如果再次执行这个函数,就会重置该函数的执行时间
 * 2. 实现方式
 *    1. 定时器
 * 3. 用法: 通常用于input输入框中input事件
 */

function debounce(fn, wait = 500) {
  let timer;
  return function () {
    //如果定时器存在就清除定时器,这一步是核心
    timer && clearTimeout(timer);
    //重新设置定时器
    timer = setTimeout(() => {
      fn.apply(null, arguments);
    }, wait);
  };
}

25. 给指定目录下的js文件头部添加内容(通常是头注释)

const fs = require("fs").promises;
const path = require("path");

//向指定目录下的js文件头部添加内容(单层)
function addData2Header(dir, writeData) {
  if (!writeData) return false;
  fs.readdir(dir)
    .then(async (files) => {
      for (let i = 0, file; (file = files[i++]); ) {
        try {
          const filePath = path.join(dir, file);
          //获取Stats对象
          const stats = await fs.stat(filePath);
          //如果是目录
          if (stats.isDirectory(filePath)) continue;
          //如果是文件
          if (stats.isFile()) {
            if (!/js/.test(path.extname(filePath))) continue;
            //读取文件中的数据
            const data = await fs.readFile(filePath);
            //将要写入的内容存入buffer
            const buff = Buffer.from(writeData + "\n");
            fs.writeFile(filePath, Buffer.concat([buff, data]))
              .then(() => console.log("内容添加成功" + "  " + filePath))
              .catch((err) => console.log(err + " " + "内容添加失败"));
          }
        } catch (error) {
          console.log(error);
        }
      }
    })
    .catch((err) => console.log(err));

//向指定目录下的js文件的头部添加内容(递归)
function addData2HeaderAll(dir, writeData) {
  if (!writeData) return false;
  fs.readdir(dir)
    .then(async (files) => {
      for (let i = 0, file; (file = files[i++]); ) {
        try {
          const filePath = path.join(dir, file);
          const stats = await fs.stat(filePath);
          if (stats.isFile()) {
            if (!/js/.test(path.extname(filePath))) continue;
            const data = await fs.readFile(filePath);
            const buff = Buffer.from(writeData + "\n");

            fs.writeFile(filePath, Buffer.concat([buff, data]))
              .then(() => console.log("内容添加成功" + "  " + filePath))
              .catch((err) => console.log(err + " " + "内容添加失败"));
            continue;
          }

          if (stats.isDirectory(filePath)) {
            addData2Header(filePath);
          }
        } catch (error) {
          console.log(error);
        }
      }
    })
    .catch((err) => console.log(err));
}


}

26.tree转数组

function tree2Arr(tree) {
  const retArr = [];
  inner(tree);
  function inner(tree) {
    tree.forEach((item) => {
      const children = item.children;
      delete item.children;
      retArr.push(item);
      if (children && Array.isArray(children)) {
        inner(children);
      }
    });
  }

  return retArr;
}

27. 动态创建二维数组


function doubleArray(x, y, init = 0) {
  return new Array(x).fill(0).map(() => new Array(y).fill(init))
}

28.二分查找

function search(nums, target) {
  let left = 0;
  let right = nums.length - 1;
  let idx;
  while (left <= right) {
    idx = ~~((left + right) / 2);
    if (nums[idx] == target) return idx;
    if (nums[idx] > target) {
      right = idx - 1;
    } else {
      left = idx + 1;
    }
  }
  return -1;
}

search([2,5,10,1,-1], 5) // 1
search([2,0,10,1,-1], 5) // -1

29. 快速排序

function quickSort(nums, left, right) {
  if (left > right) return nums;
  let middle = getMiddle(nums, left, right);
  quickSort(nums, left, middle - 1);
  quickSort(nums, middle + 1, right);
  function getMiddle(nums, left, right) {
    let baseNum = nums[left];
    while (left < right) {
      //从右向左
      while (right > left) {
        right--;
        if (nums[right] < baseNum) {
          [nums[left], nums[right]] = [nums[right], nums[left]];
          break;
        }
      }
      //从左向右
      while (left < right) {
        left++;
        if (nums[left] > baseNum) {
          [nums[right], nums[left]] = [nums[left], nums[right]];
          break;
        }
      }
    }
    return left;
  }
  return nums;
}
const nums = [  9,7,1,3,5,4,6,8,2];
quickSort(nums, 0, nums.length); // [1,2,3,4,5,6,7,8,9]

30. 匹配括号内容


//按照前序遍历
function matchBracket(str) {
  let fronts = [];
  let ends = [];
  let ret = [];
  for (let i = 0, len = str.length; i < len; i++) {
    if (str[i] == "(") {
      fronts.push(i);
      continue;
    }
    if (str[i] == ")") {
      ends.push(i);
      let frontsLen = fronts.length;
      let endsLen = ends.length
      //考虑 ) 先出现的情况
      if(endsLen > frontsLen) {
        ends.pop()
        continue
      }
      if (endsLen == frontsLen) {
        for (let j = 0; j < frontsLen; j++) {
          const frontIdx = fronts.shift();
          const endIdx = ends.pop();
          ret.push(str.slice(frontIdx + 1, endIdx));
        }
        continue
      } 
    }
  }
  return ret;
}

//按照出栈顺序遍历
function matchBracket2(str) {
  let fronts = [];
  let ret = [];
  for (let i = 0, len = str.length; i < len; i++) {
    if (str[i] == "(") {
      fronts.push(i);
      continue;
    }
    if (str[i] == ")" && fronts.length) {
      const frontIdx = fronts.pop();
      ret.push(str.slice(frontIdx + 1, i));
    }
  }
  return ret;
}
const str = "(1 + 2) * (3 / (5 - 4))";

console.log(matchBracket(str)); // [ '1 + 2', '3 / (5 - 4)', '5 - 4' ]
console.log(matchBracket2(str)); // [ '1 + 2', '5 - 4', '3 / (5 - 4)' ]

31.杨辉三角


function YHTriangle(numRows) {
  if (numRows < 1) return [];
  let triangle = [[1]];
  for (let i = 1; i < numRows; i++) {
    let ret = [];
    for (let j = 0; j < i + 1; j++) {
      // console.log(i, j);
      let pre = triangle[i - 1][j - 1];
      let current = triangle[i - 1][j] || 0;
      num = j == 0 ? current : current + pre;
      ret.push(num);
    }
    triangle.push(ret);
  }
  return triangle;
}


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

32.根据node错误优先处理回调封装promisify

function promisify(fn) {
  return  (...args) => {
    return new Promise((resolve, reject) => {
      args.push((err, ...res) => {
      //错误优先处理
        if (err) {
          reject(err);
          return;
        }
        resolve(...res);
      });
      fn(...args);
    });
  };
}

33. kebab-case 转 小驼峰

function toLowerCamelCase(str) {
  const reg = /(?:(?:^-?)|\-)([A-z])/g
  return str.trim().replace(reg, (match, p1, offset) => {
    if (offset == 0) {
      return p1 ? p1.toLowerCase() : ''
    }
    return p1 ? p1.toUpperCase() : ''
  })
}
console.log(toLowerCamelCase('-web-kit')) // webKit
console.log(toLowerCamelCase('web-kit')) // webKit
console.log(toLowerCamelCase('Web-kit')) // webKit

34. kebab-case 转大驼峰

function toUpperCamelCase(str) {
  const reg = /(?:(?:^-?)|\-)([A-z])/g
  return str.trim().replace(reg, (match, p1) => {
    return p1 ? p1.toUpperCase() : ''
  })
}

console.log(toUpperCamelCase('-web-kit')) //WebKit
console.log(toUpperCamelCase('web-kit')) //WebKit

# 35. 驼峰转成kebabcase
function toKebabCase(str) {
  const reg = /([A-Z])/g
  let ret = str.replace(reg, '-$1').toLowerCase()
  if (ret.startsWith('-')) {
    return ret.slice(1)
  }
  return ret
}

console.log(toKebabCase('webKit')) // web-kit
console.log(toKebabCase('WebKit'))  // web-kit

37. 根据身份证去获取信息

function getAnalysisIdCard(card) {
  if (!card) return false;
  //当前时间信息
  const myDate = new Date(),
    myDateYear = myDate.getFullYear(),
    myDateMonth = myDate.getMonth() + 1,
    myDateDate = myDate.getDate();

  // 用户出生信息
  const birthYear = card.substring(6, 10),
    birthMonth = card.slice(10, 12),
    birthDate = card.slice(12, 14),
    birthday = `${birthYear}-${birthMonth}-${birthDate}`;

  let age = myDateYear - birthYear;
  //
  if (birthMonth > myDateMonth) {
    age--;
  }
  if (birthMonth == myDateMonth && birthDate > myDateDate) {
    age--;
  }
  const sex = parseInt(card.substr(16, 1)) % 2 == 1 ? 1 : 2;
  return {
    age,
    birthday,
    sex,
  };
}

//单独抽取一个判断性别的函数
function getGenderByIdcard(idcard) {
  return idcard.slice(-2, -1) % 2 === 0 ? "女" : "男";
}

38 最小堆数据结构实现

class MinHeap {
  constructor() {
    this.heap = [];
  }
  size() {
    return this.heap.length;
  }

  peek() {
    if (this.size()) {
      return this.heap[0];
    }
    return null;
  }
  /* 
  
  添加的时候,向堆尾添加,向上面移动(如果从堆顶添加,整个二叉树就乱掉了)

*/
  push(node) {
    this.heap.push(node);
    // 将node传入主要是为了后面继承的改写罢了,万一node是一个对象呢?我们只需要使用node节点的某一个字段进行比较,抽出compare函数也是一样的道理,后面继承的时候直接改写compare函数就可以了
    this.shiftUp(node, this.size() - 1);
  }

  /* 
    删除节点
    使用最后一个节点替换掉第一个节点,然后向下移动
  */
  pop() {
    if (!this.size()) {
      return null;
    }
    if (this.size() === 1) {
      return this.heap.pop();
    }
    let lastNode = this.heap.pop();
    this.heap[0] = lastNode;
    this.shiftDown(this.heap[0], 0);
  }
  // 比较函数
  compare(a, b) {
    return a - b < 0;
  }
  // 变量交换
  swap(idx, parentIdx) {
    [this.heap[parentIdx], this.heap[idx]] = [
      this.heap[idx],
      this.heap[parentIdx],
    ];
  }
  // 向上移动
  shiftUp(node, idx) {
    let parentIdx = (idx - 1) >> 1;
    while (idx > 0) {
      if (this.compare(node, this.heap[parentIdx])) {
        this.swap(idx, parentIdx);
        idx = parentIdx;
        parentIdx = (idx - 1) >> 1;
      } else {
        break;
      }
    }
  }
  // 向下移动
  shiftDown(node, idx) {
    while (idx < this.size() - 1) {
      let leftIdx = (idx + 1) * 2 - 1;
      let rightIdx = leftIdx + 1;
      // 左子节点肯定有,右子节点未必有,防止出现compare函数为NaN < 0  = false 的情况,要做typeof判断一下
      if (this.compare(this.heap[leftIdx], node)) {
        // 左子节点小于父节点
        if(typeof this.heap[rightIdx] !== 'undefined' && this.compare(this.heap[rightIdx], this.heap[leftIdx])){
          // 右子节点存在并且小于左子节点
          this.swap(rightIdx, idx);
          idx = rightIdx;
        }else {
          this.swap(leftIdx, idx);
          idx = leftIdx;
        }
      } else if (typeof this.heap[rightIdx] !== 'undefined' && this.compare(this.heap[rightIdx], node)){
          // 右子节点存在并且小于父节点
          this.swap(rightIdx, idx);
          idx = rightIdx;
      }else {
        break
      }
    }    
    
  }
}

39. 支付宝小程序alipays链接转成 appId,page,query 对象形式,使用 my.navigateToMiniProgram() API 跳转小程序

function schemeToParams(schema) {
      if (!schema.startsWith('alipays:')) {
        return { message: '! 非 alipays: 开头' };
      }
      const url = schema.split('?')[1];

      let data = url.split('&');

      let appId = data[0]?.split('=')[1];
      let page, query;
      const decodeUrl = decodeURIComponent(url);

      // 有page参数就获取,没有的话默认匹配小程序首页
      if (decodeUrl.includes('page=')) {
        page = decodeURIComponent(data[1]).slice(5);
      }
      if (decodeUrl.includes('query=')) {
        if (page) {
          query = data[2] ? decodeURIComponent(data[2]).slice(6) : {};
        } else {
          query = data[1] ? decodeURIComponent(data[1])?.slice(6) : {};
        }
      }
    

      try {
        const temp = query?.split('&').map((item) => {
          return [item?.split('=')[0], decodeURIComponent(item?.split('=')[1])];
        });
        query = {};

        temp?.forEach(([key, value]) => {
          query[key] = value;
        });
      } catch (error) {
        query = {};
      }

      return {
        appId,

        page,
        query,
      };
    };