前端手写笔试题,场景题大全

139 阅读7分钟

1.一次性请求10万条

1.分堆,用requestAnimationFrame 逐个渲染

const arr = [{},{},{}]  十万条
分成1万个堆 分批次渲染,借助requestAnimation
const averageFn = (arr)=>{
    let i = 0;
    let res = []
    while(i < arr.length){
        res.push(arr.splice(i,i+10))
        i = i+10
    }
    return res
}
const res = averageFn(arr)
定义一个渲染函数
const renderArr = (page = 0)=>{
    requestAnimationFrame(()=>{
        this.arr = [...this.arr,res[page]]
        page++;
        renderArr(page)
    })
}
rederArr()

2.用表格的滚动加载,分堆后一个一个取。取了之后shift删除堆。

3.虚拟列表 vxetable

2.手动实现call,bind

 let name = 'tom'
 let person = {
     getName(a,b){
         console.log(a,b)
         return this.name
     }
 }
  Function.prototype.mycall = function(context){
     //执行.mycall前面的函数,同时将this指向man
    const arg = Array.prototype.slice(arguments,1)
    context.fn = this
    return context.fn(...arg)
 }
 person.getName.mycall(man,1,2)

 
 
 Function.prototype.mybind = function(context){
     const arg = Array.prototype.slice(arguments,1)
     const fn = this
     return fn.call(context)
 }

3.如何翻转一个字符串,

var str = '123'
str.split('').reverse().join()   str.split()可替换成[...str]或者Array.from(str)
for(let i = str.length;i--){
    res.push(str[i)
}

4 实现一个map

 Array.prototype._map = function(callback){
     var newArr = []
     for(let i = 0;i<this.length;i++){
         newArr.push(callback(this[i]),i)
     }
 }
 

5.实现一个filter

 Array.prototype._filter = function(callback){
     var newArr = []
     for(let i = 0;i<this.length;i++){
         if(callback(this[i])){
             newArr.push(this[i])
         }
     }
 }

6.获取symbol的遍历

 var person=  {
     name:'123',
     age::12,
     [Symbol('level')]:'A'
 }
 console.log(Reflect.ownKeys(person)) 获取所有的属性名
  应用场景:如果函数里有关于对象的条件运算,可以拿

7. xss攻击

脚本攻击。讲用户输入的文本进行合适的过滤,在get请求里没被服务器过滤渲染在页面上,浏览器输入(反射性),发出评论没被服务器过滤(存储型)还有dom型。比如image标签出错的钩子
预防:防止HTML出现注入,防止js执行执行恶意代码。在一些敏感数据进行加密或者转义。a标签进行白名单过滤。
预防存储型和反射型XSS攻击,服务端在提交表单时进行转义。改为纯前端渲染

8.写一个函数求和,参数不确定

    function sum(){
        let result = 0
        for(lei i = 0;i<arguments.length;i++){
            if(isNaN(arguments[i])){
                result+=arguments[i]
            }
        }
    }

9.js扁平化

    function flattern(arr){
        while(arr.some(item=>isArray(item))){
            arr = [].concat(...arr)
        }
    }
    
    
    function flattern(arr,result = []){
        for(let i = 0;i<arr.length;i++){
            if(Array.isArray(arr[i])){
                flattern(arr[i],result)
            }else{
                result.push(arr[i])
            }
        }
        return result
    }
    
    function flattern(arr){
        return  arr.tostring().join(',').map(item=>v=>+v)
    }

10 递归求和1到100

 function add(num1,num2){
     let res = num1+num2
     if(num2+1===100){
         return res
     }else{
        return add(res,num2+1)
     }
 }

11 广度优先遍历和深度优先遍历

广度bfs:取key值 一层一层取(队列)
深度dfs:每个分支取到底取不到了再去其他分支取(递归)

const obj = {
  a:{
    b:{
      z:{},
      x:{}
    },
    c:{
      d:{
         f:{
          g:1,
          h:2
        }
      }
    }
  }
}
function bfs(target){
  const queue = [target]
  const res = []
  while(queue.length){
        const _target = queue.shift()
        for(const key in _target){
          if(typeof(_target[key]) === 'object'){
              queue.push(_target[key])
          }
          res.push(key)
      }
  }
  return res
}
console.log(bfs(obj)) // 输出结果  a b c z x d f g h

function dfs(target,res=[]){
    for(const key in target){
      res.push(key)
      if(typeof(target[key]) === 'object'){
           dfs(target[key],res)
      }   
    }
    return res
}

12 数组转tree

const data = [
  { id: 1, parentId: null, name: "根节点" },
  { id: 2, parentId: 1, name: "子节点1" },
  { id: 3, parentId: 1, name: "子节点2" },
  { id: 4, parentId: 2, name: "子节点1-1" },
  { id: 5, parentId: 2, name: "子节点1-2" },
  { id: 6, parentId: 3, name: "子节点2-1" },
];
// fun1 广度优先遍历实现
// 解题思路:数组转tree,先遍历一层数组找到根节点,push到res和queue队列中。由于push的是根节点的指针,所以后续对queue循环操作也操作了res。while 循环queue队列,找到当前节点的子节点push到当前的children中,找到了再将子节点push到queqe队列中,由于一次会把所有的子节点找到,所以是广度优先,后续返回res
function arrayToTreeBfs(target,parentId = null){
    const res = []
    const queue = []
    // 先遍历一层找根节点,放入队列和tree中
    target.forEach(item=>{
        if(item.parentId === parentId){
            res.push(item)
            queue.push(item)
        }
    })
    while(queue.length){
     // 取出队列第一项 找到他的子节点放进children中,
     const _target = queue.shift()
     target.forEach(item=>{
        // 找到当前队列的所有子节点,然后将子节点也放入队列
        if(item.parentId === _target.id){
            (_target.children??=[]).push(item)
            queue.push(item)
        }
     })
    }
    return res
}
// fun2  找到根节点放入list中,再循环源数据找到每一项的父节点,放入其中
function arrayToTree(target,parentId = null){
    const res = []
    // 先遍历一层找根节点,放入队列和tree中
    target.forEach(item=>{
        if(item.parentId === parentId){
            res.push(item)
        }
    })
    // 遍历平铺的数据源每一项,找到对应的父级
    target.forEach(item=>{
        const parent = target.find(el => el.id === item.parentId)
        if(parent){
          (parent.children??=[]).push(item)
        }
        
    })
    return res
}
// fun3 先用map把每一项存起来,循环数据源,在map中找父节点,找到了则push
function arrToTreeByMap(target,parentId = {}){
    const res = []
    const map = new Map()
    target.forEach(item => {
        map.set(item.id,item)
    })
    target.forEach(item=>{
        if(item.parentId === parentId){
            res.push(item)
        }else{
            const parent = map.get(item.parentId)
            if(parent){
                (parent.children ??= []).push(item)
            }
        }
    })
    return res
}

13.请求并发的调度问题,限制最大并发数(浏览器限制6并发,不能占满,多图片上传时可用)

// 需求:实现一个任务调度器,要求最大并发数n,依次执行
// 变量:最大并发数,当前执行任务数,执行的任务队列.核心思路 调方法时将任务推进一个队列中,任务返回一个promise,保证执行完再开启一个任务.执行函数
class TaskScheduler{
    #maxCount
    #runCount
    #queue
    constructor(maxCount = 2){
        this.#maxCount = maxCount
        this.#queue = []
        this.#runCount = 0
    }
    addTask(delay,value){
        const task = ()=>{
            return new Promise((resolve)=>{
                setTimeout(()=>{
                    console.log(value)
                    resolve()
                },delay)
            })
        }
        this.#queue.push(task)
        this.runTask()
    }
    runTask(){
        if(this.#runCount >= this.#maxCount || this.#queue.length === 0) return
        const task = this.#queue.shift()
        this.#runCount++
        task().finally(()=>{
            this.#runCount--
            this.runTask()
        })
        this.runTask()
    }
}

const scheduler = new TaskScheduler(2)



scheduler.addTask(10000, 1) // 10000ms 后输出1
scheduler.addTask(5000, 2) // 5000ms 后输出2
scheduler.addTask(3000, 3) // 8000ms 后输出3
scheduler.addTask(4000, 4) // 12000ms 后输出4
scheduler.addTask(5000, 5) // 15000ms 后输出5

工作中使用调度器控制并发

  // task是一个函数返回promise(()=>接口请求)
  addTask(fn){
     const task = ()=> {
         return new Promise((resolve,reject)=>{
                fn().then(res=>resove(res))
         })
     }
     this.#queue.push(task)
     this.runTask()
  } 
 //高级写法
思路:runTask中要调task()的finally,所以task是一个函数需要返回promise将fn的promise状态传递给下一个promise,
所以  可以直接写一个promise,将task变成一个回调  借助promise.then也返回一个promise,将resolve和reject传入到then中传递给finally

 addTask(fn){
     return new Promise((...args)=>{
         this.#queue.push(()=>fn().then(...args))
         this.runTask()
     })
 }

14.实现flow的函数效果,

 flow(fn1,fn2,fn3,fn4)
const fn1 = (n) => n + 1;
const fn2 = (n) => n + 2;
const fn3 = (n) => n + 3;
const fn4 = (n) => n + 4;
const add = flow(fn1, fn2, fn3, fn4);
console.log(add(1)); 11
// 思路 传入的函数的返回值作为下个函数的参数
function flow(...fns){
    return (num)=>{
        return fns.reduce((prev,cur)=>{
           return cur(prev)
        },num)
    }
}
// 此时返回的函数只接收了一个参数,lodash中的flow是接收多个参数的
function flow(...fns){
    return (...args)=>{
          return fns.reduce((prev,cur)=>{
              return 
          },args) 
    }
}

15.闭包的运用。保存私有化变量的一种手段

  1. setup就是闭包,setup函数内的变量被render函数引用。vue2的data
export default {
    setup(){
        const count = ref()
        return {
            count
        }
    },
    template:`{{count}}`
}

2.多个promise执行时,前一个执行完才能执行下一个,不影响其他同步任务执行

    const asyncFn = createSyncFn(()=>{
        return new Promise(relove => {
            setTimeout(()=>{
                 relove(1)
            },1000)
        })
    })
    
    const asyncFn = ()=> {
        let p = Promise.resove()
        return new Promise((resolve,reject)=>{
            p = 
        })
    }

16 针对数组id相同的数据进行去重

  1. 方案一 使用map 时间复杂度为O(2n) 循环了两次不推荐
const arr = [
    {id:1,name:'张三'},
    {id:2,name:'李四'},
    {id:3,name:'王五'},
    {id:2,name:'蒸五'},
]
function unique(arr,key){
    let map = new Map()
    for(let item of arr){
        if(!map.has(item[key])){
            map.set(item[key],item)
        }
    }
    return [...map.values()]
}
  1. 方案二 使用filter 只循环一次,业务上可用
  function uniqueByKey(arr,key){
      const set = new Set()
      return arr.filter(item=>{
          if(!set.has(item[key])){
          set.add(item[key])
          return true
        }
         return false
      })
    }
  1. 方案三 将参数包装成一个回调,支持更多场景,最终版
 function uniqueByKey(arr,getKey){
      const set = new Set()
      return arr.filter(item=>{
        const key = typeof getKey === 'function'?getKey(item):getKey
        if(!set.has(key)){
          set.add(key)
          return true
        }
        return false
      })
    }
 console.log(uniqueByKey(arr,(item)=>item.id))
 console.log(uniqueByKey(arr,(item)=>`${name}-${age}`))  // age和name都相同才会被去重
 
 ts版本
  type KeyType = string | number | symbol
          function uniqueArray<T>(
            arr: T[],
            getKey: ((item: T) => KeyType) | keyof T
          ): T[] {
            const seen = new Set<KeyType>()
            return arr.filter((item) => {
              const key = typeof getKey === 'function' ? getKey(item) : item[getKey] as KeyType
              if (!seen.has(key)) {
                seen.add(key)
                return true
              }
              return false
            })
          }

          tags = uniqueArray<Tag>(tags, (item) => item.text)

17 实现loadImg函数

promse调用
loadImg('www.xxx.img').then().catch
function loadImg(url){
    return new Promise((resolve,reject) => {
        const image = new Image(url)
        imgae.url = url
        image.onsuccess = resolve
        image.onerror = reject
    })
}
参数调用
loadImg('www.xxx.img',{
    onSuccess(){},
    onError(){}
})
function loadImg(url,{onSuccess,onError}){
    const image = new Image(url)
    imgae.url = url
    image.onsuccess = onSuccess
    image.onerror = onError
}

18 实现memorize函数,相同引用的参数多次调用只执行第一次的结果 后面不执行

 // 一个参数同个引用
const fn = memorize(arr => {
  console.count('count')
  return arr.map(item => item + 1)
})
const num1 = fn([1, 2, 3])
const num2 = fn([1, 2, 3])
console.log('num1,num2 :>> ', num1, num2);

 function memorize(fn){
     const cache = new Map()
     return (args)=>{
         if(cache.has(args))return cache.get(args)
         const res = fn(args)
         cache.set(args,res)
         return res
     }
 }
// 多参数处理  因为要收集参数,只能判断参数的equal 不能做引用相等判断

 function memorize(fn){
     const cache = new Map()
     return (...args)=>{
         for(const key,value of cache){
             if(_.isEqual(key,args)) return cache.get(cache)
         }
         const res = fn(...args)
         cache.set(args,res)
         return res
     }
 }