前端手写题

105 阅读5分钟

找出字符串中最多的字符

function strMaxNum(str) {
  let obj = {};
  for (let i of str) {
    obj[i] = obj[i] ? (obj[i] += 1) : 1;
  }
  let count = 0;
  let keyName = "";

  for (let key in obj) {
    if (obj[key] > count) {
      count = obj[key];
      keyName = key;
    }
  }
  console.log("次数最多字符" + keyName + ":", count);
}

strMaxNum("12342223");

获取url地址中的参数

function getUrl(url){
   let str=url.split('?')
   if(str[0]==url){
    return ''
   }
   let obj={}
   let arr=str[1].split('&')
   for(let i=0;i<arr.length;i++){
      let sd=arr[i].split('=')
      obj[sd[0]]=sd[1]
   }
   return obj
}
getUrl('http://www.baidu.com/s?name=sd&id=23232')
{name: 'sd', id: '23232'}

数组去重的六种方式

1. new Set()


1.  function noRepeat(arr){
1.  var newArr = [...new Set(arr)]; //利用了Set结构不能接收重复数据的特点
1.  return newArr
1.  }
1.

2. filter()

var arr = ['apple','apps','pear','apple','orange','apps'];
 
console.log(arr)    
  var newArr = arr.filter(function(item,index){
     return arr.indexOf(item) === index;  // 因为indexOf 只能查找到第一个  
  });
 
console.log(newArr);

3. for+ indexOf

var arr = [1,9,8,8,7,2,5,3,3,3,2,3,1,4,5,444,55,22];
 function noRepeat(arr) {
		//定义一个新的临时数组 
		var newArr=[]; 
		//遍历当前数组 
		for(var i=0;i<arr.length;i++) {
		  //如果当前数组的第i已经保存进了临时数组,那么跳过,
		  //否则把当前项push到临时数组里面 
		  if(newArr.indexOf(arr[i]) === -1) {  //indexOf() 判断数组中有没有字符串值,如果没有则返回 -1 
		     newArr.push(arr[i]);
		  }
    	}
    return newArr
  }
  var arr2 = noRepeat(arr);
  console.log(arr2);

4. 双重for循环


var arr = [1,9,8,8,7,2,5,3,3,3,2,3,1,4,5,444,55,22];
console.log(arr);    
function noRepeat(arr){
   for (var i = 0; i < arr.length; i++) {
       for (var j = 0; j < arr.length; j++) {
           if (arr[i] == arr[j] && i != j) { //将后面重复的数删掉
              arr.splice(j, 1);
            }
       }
    }
    return arr;
}
var arr2  = noRepeat(arr);
console.log(arr2);

5. for+includes

var arr = [1,9,8,8,7,2,5,3,3,3,2,3,1,4,5,444,55,22];
    function noRepeat(arr) {
      let newArr = [];
      for(i=0; i<arr.length; i++){
        if(!newArr.includes(arr[i])){
            newArr.push(arr[i])
        }
      }
     return newArr
   }
 console.log(noRepeat(arr));

深拷贝手写

function deepClone(obj){
    let objClone=Array.isArray(obj)?[]:{};
    if(obj&&typeof obj=='object'&&obj!==null){
        for(let key in obj){
           if(obj[key]&&typeof obj[key]=='object'&&obj[key]!==null){
             objClone[key]=deepClone(obj[key])
           }else{
              objClone[key]=obj[key]
           }
            
        }
    }
    return objClone
}

let obj={a:1,b:'ss',c:false,d:null,e:undefined,f:{as:'23'},g:['23',23]}
let test=deepClone(obj)

函数柯里化

柯里化(Currying),又称部分求值(Partial Evaluation),是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。

核心思想:是把多参数传入的函数拆成单参数(或部分)函数,内部再返回调用下一个单参数(或部分)函数,依次处理剩余的参数。

function currying(fn, ...rest1) {
  return function(...rest2) {
    return fn.apply(null, rest1.concat(rest2))
  }
}

实现add(1)(2)(3)

function add(...args){
    return args.reduce(
        (a,b)=> a+b
    )
}

function currying(fn){
  let args=[]
  return function temp(...newArgs){
    if(newArgs.length){
      args = [...args, newArgs]
      return temp
    } else {
      let val = fn.apply(this, args)
      args = []
      return val
    }
  }
}
let addCurry = currying(add)
// console.log(addCurry(1)(2)(3)(4, 5)())
// console.log(addCurry(1)(2)(3, 4, 5)())
// console.log(addCurry(1)(2, 3, 4, 5)())
// console.log(add(1)(2)(3).toString())

防抖

在一段时间内,事件只会最后触发一次。

function debounce(fn,delay=500){
   let timer=null
   return function(){
    if(timer){
        return clearTimeout(timer)
    }
    timer=setTimeout(()=>{
        fn()
        timer=null
    },delay)
   }

}

节流

事件,按照一段时间的间隔来进行触发。

function throttle(fn,delay=500){
    let timer=null
    return function(){
        if(timer) return;
        timer=setTimeout(()=>{
          fn.apply(this.arguments)
          timer=null
        },delay)
    }
}

数组扁平化

将一个多维数组转换为一个一维数组 [1,[2,[3,4,5]]] ==> [1,2,3,4,5]

1、递归实现

let arr = [1, [2, [3, 4, 5]]];
function flatten(arr) {
  let result = [];

  for(let i = 0; i < arr.length; i++) {
    if(Array.isArray(arr[i])) {
      result = result.concat(flatten(arr[i]));
    } else {
      result.push(arr[i]);
    }
  }
  return result;
}
console.log(flatten(arr));  //  [1, 2, 3, 4,5]

2、reduce 函数迭代

let arr = [1, [2, [3, 4]]];
function flatten(arr) {
    return arr.reduce(function(prev, next){
        return prev.concat(Array.isArray(next) ? flatten(next) : next)
    }, [])
}
console.log(flatten(arr));//  [1, 2, 3, 45]

3、split 和 toString

var arr = [1, [2, [3, 4, 5]]];

function flatten(arr) {
    arr = arr.toString().split(',');
    var newArr = arr.map((item) => {
        item = +item
        return item
    })
    return newArr
}

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

4、通过扩展运算符实现

let arr = [1, [2, [3, 4]]];
function flatten(arr) {
    while (arr.some(item => Array.isArray(item))) {
        arr = [].concat(...arr);
    }
    return arr;
}
console.log(flatten(arr)); //  [1, 2, 3, 4,5]

5、ES6 中的 flat

let arr = [1, [2, [3, 4]]];
function flatten(arr) {
  return arr.flat(Infinity);
}
console.log(flatten(arr)); //  [1, 2, 3, 4,5]

6、使用正则和 JSON 方法

let arr = [1, [2, [3, [4, 5]]], 6];
function flatten(arr) {
  let str = JSON.stringify(arr);
  str = str.replace(/(\[|\])/g, '');
  str = '[' + str + ']';
  return JSON.parse(str); 
}
console.log(flatten(arr)); //  [1, 2, 3, 4,5]

对象扁平化

function objectFlat(obj = {}) {
  const res = {}
  function flat(item, preKey = '') {
    Object.entries(item).forEach(([key, val]) => {
      const newKey = preKey ? `${preKey}.${key}` : key
      if (val && typeof val === 'object') {
        flat(val, newKey)
      } else {
        res[newKey] = val
      }
    })
  }
  flat(obj)
  return res
}

// 测试
const source = { a: { b: { c: 1, d: 2 }, e: 3 }, f: { g: 2 } }
console.log(objectFlat(source));

冒泡排序

function bubbleSort(arr){
    let lens=arr.length
    for(let i=0;i<lens;i++){
        for(let j=0;j<lens-j-i;j++){
            if(arr[j]>arr[j+1]){
                [arr[j],arr[j+1]]=[arr[j+1],arr[j]]
            }
        }
    }
    return arr
}
bubbleSort([1,3,6,2,7])
(5) [1, 2, 3, 6, 7]

有一组版本号为:[“1.45.0”, “1.5”, “6”, “2.3.4.5”],将其排序后得到:[ ‘1.5’, ‘1.45.0’, ‘2.3.4.5’, ‘6’ ]

function verSort(arr){
    return arr.sort((a,b)=>{
        let i=0;
        const arr1=a.split('.')
        const arr2=b.split('.')
        while(true) {
           
            const s1=arr1[i]
            const s2=arr2[i]
            i++
            if(s1===undefined||s2===undefined){
                return arr1.length-arr2.length
            }
            if(s1===s2) continue;
            return s1-s2
        }
    })
}   
verSort(["1.45.0", "1.5", "6", "2.3.4.5"])
(4) ['1.5', '1.45.0', '2.3.4.5', '6']

两数求和

const twoSum = function(nums, target) {
     // 这里我用对象来模拟 map 的能力
     const diffs = {}
     // 缓存数组长度
     const len = nums.length
     // 遍历数组
     for(let i=0;i<len;i++) {
         // 判断当前值对应的 target 差值是否存在(是否已遍历过)
         if(diffs[target-nums[i]]!==undefined) {
             // 若有对应差值,那么答案get!
             return [diffs[target - nums[i]], i]
         }
         // 若没有对应差值,则记录当前值
         diffs[nums[i]]=i
     }
 };

图片懒加载

// 获取所有图片的标签
const imgs = document.getElementsByTagName('img')
// 获取可视区域的高度
const viewHeight = window.innerHeight || document.documentElement.clientHeight
// num 计算当前显示到哪一张图片,避免每次都从第一张图片开始检查是否需要
let num = 0, len = imgs.length

function lazyLoad() {
  for(let i = num; i < len; i ++) {
    // 用可视区域的高度减去匀速顶部距离可视区域的高度
    let distance = viewHeight - migs[i].getBoundingClientRect().top
    if(distance > 20){
      imgs[i].src = imgs[i].getAttribute('data-src')
    }
  }
}

// 最好添加节流
window.addEventListener('sroll', lazyLoad)

数组与树互转

数组->树

const arrtree = [
  {id:1, parentId: null, name: 'a'},
  {id:2, parentId: null, name: 'b'},
  {id:6, parentId: 3, name: 'f'},
  {id:7, parentId: 4, name: 'g'},
  {id:8, parentId: 7, name: 'h'},
  {id:3, parentId: 1, name: 'c'},
  {id:4, parentId: 2, name: 'd'},
  {id:5, parentId: 1, name: 'e'}
]

function arrayTree(arr){
  if(!Array.isArray(arr) || !arr.length) return
  let map = {}
  arr.forEach(v => map[v.id] = v)

  let list = []
  arr.forEach(v => {
    const item = map[v.parentId]
    if(item){
      ( item.children || (item.children = [])).push(v)
    } else {
      list.push(v)
    }
  })
  return list
}

// console.log(arrayTree(arrtree))


树->数组

// 将树数据转化为平铺数据
flatTreeData(treeData: any[], childKey = 'children') {
  const arr: any[] = [];
  const expanded = (data: any) => {
    if (data && data.length > 0) {
      data
        .filter((d: any) => d)
        .forEach((e: any) => {
          arr.push(e);
          expanded(e[childKey] || []);
        });
    }
  };
  expanded(treeData);
  return arr;
}

 手写 new

const customNew = (Func, ...args) => {
    // 创建一个空的简单JavaScript对象
    const obj = Object.create({})
    // 链接该对象到prototype
    obj.__proto__ = Func.prototype
    // 将创建的对象作为this的上下文 
    const res = Func.apply(obj, args)
    // 如果该函数没有返回对象,则返回this
    return res instanceof Object ? res : obj
}

手写fetch拦截器

(function() {
  let interceptor_req = [], interceptor_res = []
  
  function c_fetch(url, init = {}) {

    init.method = init.method || 'GET'

    interceptor_req.forEach(interceptor => {
      init = interceptor(init)
    })

    return new Promise((resolve, reject) => {
      fetch(url, init).then(res => {
        interceptor_res.forEach(interceptor => {
          res = interceptor(res)
        })
        resolve(res)
      }).catch(err => {
        console.error(err)
      })
    })
  }

  c_fetch.interceptor = {
    request: {
      use: function(callback){
        interceptor_req.push(callback)
      }
    },
    response: {
      use: function(callback){
        interceptor_res.push(callback)
      }
    }
  }

  // export default c_fetch

})()


手写promise

class myPromise {
    constructor(executor) {
        this.status = PROMISE_STATUS_PENDING
        this.value = undefined
        this.error = undefined
 
        this.resfns = [] //1.多次调用then 时用数组 保存
        this.errfns = []
 
        const resolve = ((value) => {
            if (this.status === PROMISE_STATUS_PENDING) {
                this.status = PROMISE_STATUS_FULFILLED
                queueMicrotask(() => {
                    this.value = value
                    this.resfns.forEach(fn => {        //循环依次调用
                        fn(this.value)
                    })
                })
            }
        })
 
        const reject = ((error) => {
            if (this.status === PROMISE_STATUS_PENDING) {
                this.status = PROMISE_STATUS_REJECTED
                queueMicrotask(() => {
                    this.error = error
                    this.errfns.forEach(fn => {
                        fn(this.error)
                    })
                })
            }
        })
        executor(resolve, reject)
    }
 
    then(resfn, errfn) {        //将多次调用的then 方法保存到数组中,依次调用
        this.resfns.push(resfn)
        this.errfns.push(errfn)
    }
}

instanceof

function isInstanceOf(instance, klass) {
  let proto = instance.__proto__
  let prototype = klass.prototype
  while (true) {
    if (proto === null) return false
    if (proto === prototype) return true
    proto = proto.__proto__
  }
}

// 测试
class Parent {}
class Child extends Parent {}
const child = new Child()
console.log(isInstanceOf(child, Parent), isInstanceOf(child, Child), isInstanceOf(child, Array));

随机生成颜色

function rgb(){//rgb颜色随机
                var r = Math.floor(Math.random()*256);
                var g = Math.floor(Math.random()*256);
                var b = Math.floor(Math.random()*256);
                var rgb = '('+r+','+g+','+b+')';
                return rgb;
            }
 function color16(){//十六进制颜色随机
                var r = Math.floor(Math.random()*256);
                var g = Math.floor(Math.random()*256);
                var b = Math.floor(Math.random()*256);
                var color = '#'+r.toString(16)+g.toString(16)+b.toString(16);
                return color;
            }

异步并发数限制

/**
 * 关键点
 * 1. new promise 一经创建,立即执行
 * 2. 使用 Promise.resolve().then 可以把任务加到微任务队列,防止立即执行迭代方法
 * 3. 微任务处理过程中,产生的新的微任务,会在同一事件循环内,追加到微任务队列里
 * 4. 使用 race 在某个任务完成时,继续添加任务,保持任务按照最大并发数进行执行
 * 5. 任务完成后,需要从 doingTasks 中移出
 */
function limit(count, array, iterateFunc) {
  const tasks = []
  const doingTasks = []
  let i = 0
  const enqueue = () => {
    if (i === array.length) {
      return Promise.resolve()
    }
    const task = Promise.resolve().then(() => iterateFunc(array[i++]))
    tasks.push(task)
    const doing = task.then(() => doingTasks.splice(doingTasks.indexOf(doing), 1))
    doingTasks.push(doing)
    const res = doingTasks.length >= count ? Promise.race(doingTasks) : Promise.resolve()
    return res.then(enqueue)
  };
  return enqueue().then(() => Promise.all(tasks))
}

// test
const timeout = i => new Promise(resolve => setTimeout(() => resolve(i), i))
limit(2, [1000, 1000, 1000, 1000], timeout).then((res) => {
  console.log(res)
})

异步串行 | 异步并行

// 字节面试题,实现一个异步加法
function asyncAdd(a, b, callback) {
  setTimeout(function () {
    callback(null, a + b);
  }, 500);
}

// 解决方案
// 1. promisify
const promiseAdd = (a, b) => new Promise((resolve, reject) => {
  asyncAdd(a, b, (err, res) => {
    if (err) {
      reject(err)
    } else {
      resolve(res)
    }
  })
})

// 2. 串行处理
async function serialSum(...args) {
  return args.reduce((task, now) => task.then(res => promiseAdd(res, now)), Promise.resolve(0))
}

// 3. 并行处理
async function parallelSum(...args) {
  if (args.length === 1) return args[0]
  const tasks = []
  for (let i = 0; i < args.length; i += 2) {
    tasks.push(promiseAdd(args[i], args[i + 1] || 0))
  }
  const results = await Promise.all(tasks)
  return parallelSum(...results)
}

// 测试
(async () => {
  console.log('Running...');
  const res1 = await serialSum(1, 2, 3, 4, 5, 8, 9, 10, 11, 12)
  console.log(res1)
  const res2 = await parallelSum(1, 2, 3, 4, 5, 8, 9, 10, 11, 12)
  console.log(res2)
  console.log('Done');
})()

call

Function.prototype.myCall = function (context = globalThis) {
  // 关键步骤,在 context 上调用方法,触发 this 绑定为 context,使用 Symbol 防止原有属性的覆盖
  const key = Symbol('key')
  context[key] = this
  // es5 可通过 for 遍历 arguments 得到参数数组
  const args = [...arguments].slice(1)
  const res = context[key](...args)
  delete context[key]
  return res
};

// 测试
const me = { name: 'Jack' }
function say() {
  console.log(`My name is ${this.name || 'default'}`);
}
say.myCall(me)

apply

Function.prototype.myApply = function (context = globalThis) {
  // 关键步骤,在 context 上调用方法,触发 this 绑定为 context,使用 Symbol 防止原有属性的覆盖
  const key = Symbol('key')
  context[key] = this
  let res
  if (arguments[1]) {
    res = context[key](...arguments[1])
  } else {
    res = context[key]()
  }
  delete context[key]
  return res
}

// 测试
const me = { name: 'Jack' }
function say() {
  console.log(`My name is ${this.name || 'default'}`);
}
say.myApply(me)

bind

Function.prototype.myBind = function (context = globalThis) {
  const fn = this
  const args = Array.from(arguments).slice(1)
  const newFunc = function () {
    const newArgs = args.concat(...arguments)
    if (this instanceof newFunc) {
      // 通过 new 调用,绑定 this 为实例对象
      fn.apply(this, newArgs)
    } else {
      // 通过普通函数形式调用,绑定 context
      fn.apply(context, newArgs)
    }
  }
  // 支持 new 调用方式
  newFunc.prototype = Object.create(fn.prototype)
  return newFunc
}

// 测试
const me = { name: 'Jack' }
const other = { name: 'Jackson' }
function say() {
  console.log(`My name is ${this.name || 'default'}`);
}
const meSay = say.myBind(me)
meSay()
const otherSay = say.myBind(other)
otherSay()