经典前端笔试题

380 阅读5分钟

一些非算法前端代码题,面试中要是碰到,没练习过一时半会很可能出不来,出不来就等着凉凉了。

获取详细的数据类型

实现一个 getType 函数,传入一个变量,能准确的获取它的类型。 如 number string function object array map regexp 等。

    function getType(x){
      const originType = Object.prototype.toString.call(x) // '[obeject String]'
      const spaceIndex = originType.indexOf(' ')
      const type = originType.slice(spaceIndex + 1,-1)
      return type.toLowerCase()
    }

手写new

function myNew(constructor,...args){ 
//创建一个全新的对象,这个新对象会被执行原型连接,及继承 constructor 的原型
const obj = object.create(constructor.protype); 
//这个新对象会绑定到函数调用的 `this`,及将 obj 作为 this ,执行 constructor ,传入参数
constructor.apply(obj,args); 
//返回
return obj 
}

lazyman

class LazyMan{
    tasks = []
    constructor(name){
        this.name = name
        setTimeout(() =>{
            this.next()
        })
    }
    
    next() {
        const task = this.tasks.shift() // 取出当前 tasks 的第一个任务
        if (task) task()
    }
    
    eat(food) {
        const task = () =>{
            console.log(`eat${food}`)
            this.next() // 立刻执行下一个任务
        }
        this.tasks.push(task);
        return this // 链式调用
    }
    
    sleep(seconds) {
        const task = () => {
            console.log(`${this.name} 开始睡觉`);
            setTimeout(()=>{
            console.log(`${this.name}醒来 ${seconds}s,执行下一个任务`)
            this.next() // xx 秒之后再执行下一个任务
            },seconds*1000)
        }
        this.tasks.push(task);
        return this //链式调用
    }
}

const me = new LazyMan('neos')
me.eat('苹果').eat('香蕉').sleep(2).eat('葡萄').eat('西瓜').sleep(2).eat('橘子')

curry 函数

    function curry(fn){
        const fnArgsLength = fn.length // 传入函数的参数长度
        let args = []
        return function calc(...newArgs){
            args = [
                ...args,
                ...newArgs
            ];
            if (args.length < fnArgsLength){
                   // 参数不够,返回函数
                    return calc
            }else {
                return fn.apply(this,args.slice(0,fnArgsLength))
            }
        }
    }
    function add(a, b ,c) {
         return a + b + c
     }
     add(10, 20, 30) // 60

     const curryAdd = curry(add)
     const res = curryAdd(10)(20)(30) // 60
     console.info(res)

手写bind

    // context 是 bind 传入的 this
    // bindArgs 是 bind 传入的各个参数
    Function.prototype.myBind = function(context,...bindArgs){
        const self = this // 当前的函数本身
        return function(...args){
            const newArgs = bindArgs.concat(args);
            return self.apply(context,newArgs)
        }
    }
    function fn( a, b, c) {
        console.info(this, a, b, c)
    }

    const fn1 = fn.myBind({x: 100}, 10)
    fn1(20, 30)

手写call

Function.prototype.myCall  = function (context,...args){
    if (content == null) context = globalThis;
    if(typeof context !== 'object') context = new Object(context);
    const fnKey = Symbol;// 不会出现属性名称的覆盖
    context[fnKey] = this // this 就是当前的函数
    const res = context[fnKey](...args) 绑定了 this
    delete context[fnKey] //清理掉 fn ,防止污染
    return res
} 

手写apply

Function.prototype.myApply = function(context,args){
    if(context == null) context = globalThis;
    if(typeof context !=='object') context = newObject(context);
    const fnKey = Symbol;
    const context[fnKey] = this;
    const res = context[fnKey](args);
    delete context[fnKey]
    return res;
} 

Debounce

   function debounce(func, delay = 200) {
      let timeOut = null;
      return function (...args) {
        clearTimeout(timeOut);
        timeOut = setTimeout(() => {
          if (typeof func === 'function') {
            func(...args);
          }
        }, delay);
      };
   }

Throttle

     function throttle(fn, delay = 100) {
        let timer = 0
        return function () {
            if (timer) return
              timer = setTimeout(() => {
                 func(...args);
                 timer = 0
           }, delay)
      }
    }

手写instanceof

function testInstancof(intance,origin){
    if(instance == null) return false; 
    const type = typeof instance; 
    if(type !=='object' && type !== 'function') return false; 
    let tempInstance = instance; 
    While(tempInstance){ 
    if(tempInstance.__proto === origin.prototype) 
    return true; 
    tempInstance = tempInstance.__proto__ 
    } 
    return false 
}

数组扁平化,使用 push

function flatten(arr){
    const res = [];
    arr.forEach(item =>{
        if (Array.isArray(item)){
            item.forEach(n => res.push(n))
        } else {
            res.push(item)
        }
    })
    return res
}

reduce结合concat实现数组扁平化

const arr = [
    [1,2],
    [3,4],
    [5,6]
].reduce((arr,cur) =>{
    return acc.concat(cur)
},[])

数组深度扁平化,使用 push

function flattenDeep(arr) {
    const res = []
    arr.forEach(item => {
        if (Array.isArray(item)) {
            const flatItem = flattenDeep(item) // 递归
            flatItem.forEach(n => res.push(n))
        } else {
            res.push(item)
        }
    })
    return res
}

reduce结合concat数组深度扁平化

    const arr3 = [\
     [12],\
     [34],\
     [5, [7, [910], 8], 6],\
    ];
    
    const flatten = arr => {
        arr.reduce(
            (pre,cur) => {pre.concat(Array.isArray(cur)?flatten(cur):cur)},[])
    }
    
    
   console.log(flatten(arr3)); // [ 1, 2, 3, 4, 5, 7, 9, 10, 8, 6 ] 

es6 flat(),flatMap()

es6.ruanyifeng.com/?search=fal…

[1, 2, [3, 4]].flat()
// [1, 2, 3, 4]
[1, [2, [3]]].flat(Infinity)
// [1, 2, 3]

数组转树(复杂度要考虑)

function convert(arr) {
    // 用于 id 和 item 的映射
    const idToTreeNode = new Map();
    let result = [];
    arr.forEach(item => {
        idToTreeNode.set(item.id, item)
    })
    arr.forEach(item => {
        const { id, name, parentId } = item
        // 找到 parentNode 并加入到它的 children
        const parentNode = idToTreeNode.get(parentId);
        if (parentNode) {
            if (parentNode.children == null) parentNode.children = []
            parentNode.children.push(item)
         }else{
             // 如果他没有父亲就把他加入到我们首次声明的数组里 
             result.push(item);
       }
    })
    return result;
}

const arr = [
    { id: 3, name: '部门C', parentId: 1 },
    { id: 4, name: '部门D', parentId: 2 },
    { id: 5, name: '部门E', parentId: 2 },
    { id: 6, name: '部门F', parentId: 3 },
    { id: 1, name: '部门A', parentId: 0 }, // 0 代表顶级节点,无父节点
    { id: 2, name: '部门B', parentId: 1 },
]
const tree = convert(arr)
console.info(tree)

经典的算法接上一题算是扩展,降低时间复杂度

给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target  的那 两个 整数,并返回它们的数组下标。

你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。

你可以按任意顺序返回答案。

例1: 
输入:nums = [2,7,11,15],
target = 9 输出:[0,1] 
解释:因为 nums[0] + nums[1] == 9 返回 [0, 1] 。

例2: 
输入: nums = [3,2,4],
target = 6 输出: [1,2] 

例3: 
输入: nums = [3,3],
target = 6 输出: [0,1]

function twoSum(nums,target){
    const map = new Map();
    for(let i=0,len = num.length;i<len;i++){
        // 一层遍历,用 target 减去每一项,在 map 的 key 中寻找
        if(map.has(target - num[i])){
            // 存在则返回结果
            return [map.get(target - num[i]),i]
        }
        // 不存在,则设置 map key 和 val
        map.set(nums[i],i)
    }
}

深度优先遍历(递归和不用递归)

let treeData=[
	   {
		   id:1,
		   children:[{
			   id:2
		   },{
			   id:3,
			   children:[
				   {
					   id:5
				   }
			   ]
		   }]
	   },{
		id:4
	   }
   ]
**递归**
function depthFirstTraverse(treeData) {
    if (treeData.length>0) {
        treeData.forEach(item => {
            console.log(item.id)
            item.children&&depthFirstTraverse(item.children) // 递归
        })
    }
}

**非递归,用栈**
function depthFirstTraverse(treeData) {
    const stack = [];
    //第一层压栈
    Array.from(treeData).reverse().forEach(
        item => stack.push(item)
    )
    while(stack.length>0){
        const node = stack.pop();// 出栈
        console.log(node.id);
        const children = node.children;
        if(children&&children.length>0){
            //reverse 反顺序压栈
             Array.from(children).reverse().forEach(
                    item => stack.push(item)
              )
        }
    }
}


广度优先遍历

let treeData=[
	   {
		   id:1,
		   children:[{
			   id:2
		   },{
			   id:3,
			   children:[
				   {
					   id:5
				   }
			   ]
		   }]
	   },{
		id:4
	   }
  ]
   
function breadthFirstTraverse(treeData) {
    const queue = [] 

    // 第一层入队列
    treeData.forEach(item =>queue.unshift(item));
    while (queue.length > 0) {
        const item = queue.pop()
        if (item == null) break
        console.log(item.id)
        // 子节点入队
        const children = item.children
        if (children&&children.length) {
            children.forEach(item => queue.unshift(item))
        }
    }
}

深拷贝

function cloneDeep(obj,map = new WeakMap()){
    if (typeof obj !== 'object' || obj == null ) return obj;
    // 避免循环引用
    const objFromMap = map.get(obj);
    if (objFromMap) return objFromMap;
    let target = {};
    map.set(obj,target);
    // Map
    if(obj instanceof Map){
        target = new Map();
        obj.forEach((v,k) => {
            const v1 = cloneDeep(v,map);
            const k1 = cloneDeep(k,map);
            target.set(k1,v1)
        })
    }
    
    // Set
    if(obj instanceof Set){
        target = new Set();
        obj.forEach(v =>{
            const v1 = cloneDeep(v,map);
            target.add(v1);
        })
    }
    
    //Array
      if (obj instanceof Array) {
        target = obj.map(item => cloneDeep(item, map))
    }
    
    //Object
    for (const key in obj){
        const val = obj[key];
        const val1 = cloneDeep(val,map);
        target[key] = val1;
    }
    return target
}

// 功能测试
const a = {
     set: new Set([10, 20, 30]),
    map: new Map([['x', 10], ['y', 20]]),
     info: {
         city: '北京'
     },
     fn: () => { console.info(100) }
 }
 a.self = a
 console.log( cloneDeep(a) )

自定义事件

class EventBus {
    /**
     * {
     *    'key1': [
     *        { fn: fn1, isOnce: false },
     *        { fn: fn2, isOnce: false },
     *        { fn: fn3, isOnce: true },
     *    ]
     *    'key2': [] // 有序
     *    'key3': []
     * }
     */

    constructor() {
        this.events = {}
    }

    on(type, fn, isOnce) {
        const events = this.events
        if (events[type] == null) {
            events[type] = [] // 初始化 key 的 fn 数组
        }
        events[type].push({ fn, isOnce })
    }

    once(type, fn) {
        this.on(type, fn, true)
    }

    off(type, fn) {
        if (!fn) {
            // 解绑所有 type 的函数
            this.events[type] = []
        } else {
            // 解绑单个 fn
            const fnList = this.events[type]
            if (fnList) {
                this.events[type] = fnList.filter(item => item.fn !== fn)
            }
        }
    }

    emit(type, ...args) {
        const fnList = this.events[type]
        if (fnList == null) return

        // 注意
        this.events[type] = fnList.filter(item => {
            const { fn, isOnce } = item
            fn(...args)

            // once 执行一次就要被过滤掉
            if (!isOnce) return true
            return false
        })
    }
}

// const e = new EventBus()

// function fn1(a: any, b: any) { console.log('fn1', a, b) }
// function fn2(a: any, b: any) { console.log('fn2', a, b) }
// function fn3(a: any, b: any) { console.log('fn3', a, b) }

// e.on('key1', fn1)
// e.on('key1', fn2)
// e.once('key1', fn3)
// e.on('xxxxxx', fn3)

// e.emit('key1', 10, 20) // 触发 fn1 fn2 fn3

// e.off('key1', fn1)

// e.emit('key1', 100, 200) // 触发 fn2

LRU

class LRUCache {
    private length: number
    private data: Map<any, any> = new Map()

    constructor(length: number) {
        if (length < 1) throw new Error('invalid length')
        this.length = length
    }

    set(key: any, value: any) {
        const data = this.data

        if (data.has(key)) {
            data.delete(key)
        }
        data.set(key, value)

        if (data.size > this.length) {
            // 如果超出了容量,则删除 Map 最老的元素
            const delKey = data.keys().next().value
            data.delete(delKey)
        }
    }

    get(key: any): any {
        const data = this.data

        if (!data.has(key)) return null

        const value = data.get(key)

        data.delete(key)
        data.set(key, value)

        return value
    }
}

// const lruCache = new LRUCache(2)
// lruCache.set(1, 1) // {1=1}
// lruCache.set(2, 2) // {1=1, 2=2}
// console.info(lruCache.get(1)) // 1 {2=2, 1=1}
// lruCache.set(3, 3) // {1=1, 3=3}
// console.info(lruCache.get(2)) // null
// lruCache.set(4, 4) // {3=3, 4=4}
// console.info(lruCache.get(1)) // null
// console.info(lruCache.get(3)) // 3 {4=4, 3=3}
// console.info(lruCache.get(4)) // 4 {3=3, 4=4}

Proxy用法(代理沙箱)

let defaultValue = {} // 子应用的沙箱容器 
export class ProxySandbox{ 
constructor() {
    this.proxy = null; 
    this.active() 
} // 沙箱激活
active() { 
    // 子应用需要设置属性,
    this.proxy = new Proxy(window, { 
        get(target, key) { 
            return defaultValue[key] || target[key] 
        },
        set(target, key, value) {
            defaultValue[key] = value 
            return true
            } 
        }
    ) 
}
// 沙箱销毁
inactive () { 
    defaultValue = {} 
}
}