前端手写题(二)

89 阅读7分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

二、数据处理

1. 实现数组的乱序排序

  1. 使用sort,对数组进行排序
  • 每次从数组中挑选两个值进行
  • 大于等于0不交换位置
  • 小于0进行交换
  • Math.random() - 0.5大于小于0是随机出现的,所以数组随机排序
function shuffle(arr) {
  arr.sort(() => {
    Math.random() - 0.5
  })
  return arr;
}
const arr = [1,2,3,4,5,6,7,8,9,10];
console.log(shuffle(arr))
  1. 使用Math.random()随机生成索引值
  • 将第一个元素与随机生成的索引值的元素进行交换
  • 将第二个元素与随机生成的索引值的元素进行交换
  • 按照上边规律直到遍历完
function shuffle(arr) {
  for (let i = 0; i < arr.length; i++) {
    var randomIndex = parseInt(arr.length * Math.random());
    [arr[i], arr[randomIndex]] = [arr[randomIndex], arr[i]];
    console.log(arr)
  }
  return arr;
}
const arr = [1,2,3,4,5,6,7,8,9,10];
console.log(shuffle(arr)) // [2, 3, 6, 5, 8, 9, 7, 1, 4, 10]

2. 实现数组元素求和

  1. 一维数组
  • 循环求和
function arrSum(arr) {
  let sum = 0
  arr.forEach(item => {
    sum += item
  })
  return sum;
}

const arr = [1,3,6,9]
console.log(arrSum(arr)) // 19
  • 使用reduce求和,reduce是将上次得到的结果作为这次的参数
let sum = arr.reduce((total,i) => {
  return total + i
})

const arr = [1,3,6,9]
console.log(sum); // 19
  1. 多维数组
  • 先拍平载再求和
const arr1 = [1,[3,[6]],9]
let sum1 = arr1.flat(Infinity).reduce((prev,next) => {
  return prev + next
},0)
console.log(sum1);
  1. 对象数组
let arr2 = [{a:1, b:3, c:6}, {a:3, b:3}, {a:3}] 
let sum2 = arr2.reduce((prev,next) => {
  return prev + next['a']
},0)
console.log(sum2) // 7

3. 实现数组扁平化

  1. es6中的flat
function flatten(arr) {
  // arr.flat(Infinity) 不论多少层都拍平
  return arr.flat(2)
}
const arr = [1,[2,[3,4]],5]
console.log(flatten(arr)) // [1, 2, 3, 4,5]
  1. 扩展运算符:只能展开一层数组
  • 若arr中含有数组则使用一次扩展运算符,直至没有为止
function flatten(arr) {
  while(arr.some(item => Array.isArray(item))) {
    arr = [].concat(...arr)
  }
  return arr;
}
const arr = [1,[2,[3,4]],5]
console.log(flatten(arr)) // [1, 2, 3, 4,5]
  1. 普通递归
  • 通过遍历查找是否还有数组,有数组进行递归,不是数组的话,push到结果中
function flatten(arr) {
  let res = [];
  arr.forEach(item => {
    if (Array.isArray(item)) {
      res = res.concat(flatten(item))
    }
    else {
      res.push(item)
    }
  });
  return res;
}
const arr = [1,[2,[3,4]],5]
console.log(flatten(arr)) // [1, 2, 3, 4,5]

实现数组的flat方法

  1. reduce + 递归
  • 上次的结果作为参数传递,最后返回prev
function flatten(arr) {
  return arr.reduce((prev,next) => {
    return prev.concat(Array.isArray(next) ? flatten(next) : next)
  },[])
}

const arr = [1,[2,[3,4]],5]
console.log(flatten(arr)) // [1, 2, 3, 4,5]
  1. toString转为字符串,再split转为数组
function flatten(arr) {
  let res = arr.join(',').split(',');
  return res
}

const arr = [1,[2,[3,4]],5]
console.log(flatten(arr)) // [1, 2, 3, 4,5]

4. 实现数组去重

  1. new Set,可对NaN去重
function unique(arr) {
  return [...new Set(arr)]
}
const arr = [1,2,1,NaN,undefined,NaN,undefined,true,'abc',true,'abc']
console.log(unique(arr))
// [1, 2, NaN, undefined, true, 'abc']
  1. 使用indexOf判断res中是否存在,无法对NaN去重
function unique(arr) {
  let res = [];
  arr.forEach(item => {
    if (res.indexOf(item) === -1) {
      res.push(item)
    }
  })
  return res;
}
const arr = [1,2,1,NaN,undefined,NaN,undefined,true,'abc',true,'abc']
console.log(unique(arr))
//  [1, 2, NaN, undefined, NaN, true, 'abc']
  1. 使用includes判断res中是否存在,可对NaN去重
function unique(arr) {
  let res = [];
  arr.forEach(item => {
    if (!res.includes(item)) {
      res.push(item)
    }
  })
  return res;
}
const arr = [1,2,1,NaN,undefined,NaN,undefined,true,'abc',true,'abc']
console.log(unique(arr))
//  [1, 2, NaN, undefined, true, 'abc']
  1. 使用map,has()判断是否存在,不存在set()添加,可对NaN去重
function unique(arr) {
  const map = new Map();
  const res = [];
  arr.forEach(item => {
    // 判断是否包含item的属性值
    if (!map.has(item)) {
      // 将item设置到map中,并设置属性值为true
      map.set(item, true)
      res.push(item)
    }
  })
  return res;
}
const arr = [1,2,1,NaN,undefined,NaN,undefined,true,'abc',true,'abc']
console.log(unique(arr))
//  [1, 2, NaN, undefined, true, 'abc']

5. 实现数组push方法

  • push方法传参参数不确定,使用arguments
  • push返回数组长度,并将值添加到数组中
Array.prototype.myPush = function() {
  // 判断是否有this.length
  if (!this.length) {
    this.length = 0;
  }
  if (isNaN(Number(this.length))) {
    this.length = 0;
  }
  for (let i = 0;i < arguments.length; i++) {
    this[this.length] = arguments[i]
  }
  return this.length;
}

const arr = [1,'abc']
console.log(arr.myPush(3,'aa'),arr) // [1, 'abc', 3, 'aa']
console.log(arr.push(3,'aa'),arr)

6. 实现数组的filter方法

  • filter传入一个带有条件的函数
  • 返回能满足此条件的值数组
  • filter不会改变原数组
Array.prototype.myFilter = function(fn) {
  if (typeof fn !== 'function') {
    throw Error('参数必须是一个函数')
  }
  const res = [];
  for(let i = 0;i < this.length;i++) {
    // 满足条件将this[i]添加到结果中
    fn(this[i]) && res.push(this[i]);
  }
  return res;
}

const arr = [1,3,6,9]
const res = arr.myFilter(item => item > 3 && item < 9) // [6]
// const res = arr.filter(item => item > 3 && item < 9)
console.log(res)

7. 实现数组的map方法

  • map传入函数
  • 返回一个数组
  • map不会改变原数组
Array.prototype.myMap = function(fn) {
  if (typeof fn !== 'function') {
    throw Error('参数必须是一个函数')
  }
  const res = [];
  for(let i = 0;i < this.length;i++) {
    res.push(fn(this[i]))
  }
  return res;
}

const arr = [1,2,3,5]
const res = arr.myMap(item => {
  return item * 2
})
// const res = arr.map(item => {
//   return item * 2
// })
console.log(res) // [2, 4, 6, 10]

8. 实现类数组转化为数组

类数组具备两个特性:

  • 数字作为属性名称
  • 具有length属性

类数组设计的目的更多的是遍历访问下标,而不是添加和删除元素

  • 调用数组的slice
const arrayLike = {
  0: '类数组',
  1: '数字作为属性名称',
  2: '具备length属性',
  length: 3
}

const res = Array.prototype.slice.call(arrayLike);
console.log(res) // ['类数组', '数字作为属性名称', '具备length属性']
  • 调用数组的splice
const arrayLike = {
  0: '类数组',
  1: '数字作为属性名称',
  2: '具备length属性',
  length: 3
}

const res = Array.prototype.splice.call(arrayLike, 0);
console.log(res) // ['类数组', '数字作为属性名称', '具备length属性']
  • Array.from
const arrayLike = {
  0: '类数组',
  1: '数字作为属性名称',
  2: '具备length属性',
  length: 3
}

const res = const res = Array.from(arrayLike);
console.log(res) // ['类数组', '数字作为属性名称', '具备length属性']
  • 调用数组的concat,使用apply
const arrayLike = {
  0: '类数组',
  1: '数字作为属性名称',
  2: '具备length属性',
  length: 3
}

const res = Array.prototype.concat.apply([], arrayLike);
console.log(res) // ['类数组', '数字作为属性名称', '具备length属性']

9. 实现字符串翻转

function myReverse(str) {
  if (typeof str !== 'string') {
    throw Error('参数必须是一个字符串')
  }
  return str.split('').reverse().join('')
}

const str = 'abc';
console.log(myReverse(str)) // 'cba'

10. 实现字符串的repeat方法

repeat(count)方法是重复填写count个str

  • new Array(count + 1)表示数组长度为count + 1的空数组
  • 通过str分隔转为字符串
function myRepeat(str, count) {
  return new Array(count + 1).join(str);
}

const str = 'abc';
console.log(myRepeat(str,2)) // 'abcabc'
// console.log(str.repeat(2))

11. 解析url变为对象形式

  • 手写函数
function query(url) {
  let args = url.split('?');
  if (args[0] === url) {
    return ''
  }
  let params = args[1].split('&')
  let res = {}
  params.forEach(item => {
    let arr = item.split('=');
    const key = arr[0];
    const val = arr[1];
    res[key] = val;
  })
  return res;
}

console.log(query("http://127.0.0.1/learnjs/index.html?a=1&b=&=3"));
// {a: '1', b: '', "": '3'}

12. 交换a,b的值

  1. es6解构赋值
let a = 1;
let b = 2;

[a,b] = [b,a]

console.log(a,b) // 2 1
  1. 临时变量
let a = 1;
let b = 2;

let tmp = a;
a = b;
b = tmp;

console.log(a,b) // 2 1
  1. 使用加法
let a = 1;
let b = 2;

a = a + b;
b = a - b;
a = a - b;

console.log(a,b) // 2 1

13. 将数字每千分位用逗号隔开

  1. 使用toLocaleString()方法
function handleNum(num) {
  return num.toLocaleString();
}

const num = 1010101.0246;
// const num = 100 // 100
console.log(handleNum(num)) // 1,010,101.025
  1. 手写函数
function handleNum(n) {
  let num = n.toString();
  let res;
  // 判断有无小数点,有的话加上.
  let decimals = '';
  if (num.indexOf('.') > -1) {
    decimals = `.${num.split('.')[1]}`;
    res = num.split('.')[0];
  }
  else {
    decimals = '';
    res = num;
  }
  // 根据长度对3取余,余数+中间+小数
  let length = res.length;
  if (length <= 3) {
    return num;
  }
  else {
    let remainder = res % 3;
    const start = res.slice(0, remainder);
    // \d{n}几位数字,/g返回所有的匹配
    const middle = res.slice(remainder, length).match(/\d{3}/g).join(',');
    return `${start},${middle}${decimals}`;
  }
}

const num = 1010101.0246;
// const num = 100 // 100
console.log(handleNum(num)) // 1,010,101.0246

14. 实现非负大整数相加

大整数,超过Number.MAX_SAFE_INTEGER的整数进行相加,使用+不行,超过Number.MAX_SAFE_INTEGER数字会被立即转换为科学计数法,并且数字精度相比以前将会有误差

  • 取两个值的最大长度,另一个用0补齐
  • 从个位数开始相加singularSum
  • 计算进位数,Math.floor(singularSum/10) carry
  • 将此次结果保存到字符串中
  • 循环到相加完成
function sumBigNumber(a,b) {
  let maxLength = Math.max(a.length,b.length);
  a = a.padStart(maxLength, 0);
  b = b.padStart(maxLength, 0);

  let sum = '';
  let singularSum = 0; // 单数相加结果
  let carry = 0; // 进位

  for (let i = maxLength -1;i>= 0; i--) {
    singularSum = parseInt(a[i]) + parseInt(b[i]) + carry;
    carry = Math.floor(singularSum/10);
    sum = singularSum % 10 + sum;
  }
  if (carry == 1) {
    sum = "1" + sum;
  }
  return sum;
}

const num1 = '9007199254740991';
const num2 = '1234567899999999999';
console.log(sumBigNumber(num1,num2)); // 1243575099254740990

15. add(1)(2)(3)柯里化

柯里化是函数编程中的概念,主要是传递给参数一部分参数,返回一个函数接着来处理剩下的参数,直到处理完所有的参数

function add (...args) {
  return args.reduce((prev,next) => {
    return prev + next;
  }, 0)
}
function currying(fn) {
  let args = [];
  return function tmp(...newArgs) {
    if (newArgs.length) {
      args = [...args,...newArgs];
      return tmp;
    }
    else {
      let val = fn.apply(this, args);
      args = [];
      return val;
    }
  }
}
let addCurry = currying(add)

console.log(addCurry(1)(2)(3)(4)()); // 10
console.log(addCurry(1)(2)(3,4)()); // 10
console.log(addCurry(1)(2,3,4)()); // 10

16. 使用es5,es6求函数参数的和

  1. es5
function sum() {
  let sum = 0;
  Array.prototype.forEach.call(arguments, item => {
    sum += item;
  })
  return sum;
}

console.log(sum(1,2,3)) // 6
  1. es6
function sum(...args) {
  let sum = 0;
  args.forEach(item => {
    sum += item;
  })
  return sum;
}

console.log(sum(1,2,3)) // 6

17. 实现日期格式化函数

function dateFormat(dateInput,format) {
  let day = dateInput.getDate();
  let month = dateInput.getMonth() + 1;
  let year = dateInput.getFullYear();

  format = format.replace(/yyyy/, year);
  format = format.replace(/mm/, month);
  format = format.replace(/dd/, day);
  return format;
}

console.log(dateFormat(new Date('2022-1-1'), 'yyyy/mm/dd')) // 2022/1/1
console.log(dateFormat(new Date('2022-1-1'), 'yyyy年mm月dd日')) // 2022年1月1日

18. 将js对象转为树形结构

在项目中,有时需要将后端返回的列表集合按照父子转为树形结构

转换前后:

const jsonLike = [
  {
    id: 1,
    pid: 0,
    name: 'body'
  },
  {
    id: 2,
    pid: 1,
    name: 'div'
  },
  {
    id: 3,
    pid: 2,
    name: 'title'
  }
]

console.log(jsonToTree(jsonLike))
// 转换后结果:
tree = [{
  id: 1,
  pid: 0,
  name: 'body',
  children: [{
    id: 2,
    pid: 1,
    name: 'div',
    children: [{
      id: 3,
      pid: 2,
      name: 'title'
    }]
  }
}]

代码实现:

function jsonToTree(data) {
  let res = [];
  if (!Array.isArray(data)) {
    return res;
  }
  let map = {};
  data.forEach(item => {
    map[item.id] = item;
  })
  data.forEach(item => {
    let parent = map[item.pid];
    if (parent) {
      if (!parent['children']) {
        parent['children'] = []
      }
      parent['children'].push(item);
    }
    else {
      res.push(item);
    }
  })

  return res;
}