前端面试题-手写题(整理)

275 阅读2分钟

手写 JS 函数,实现数组扁平化 Array Flatten

只减少一级嵌套

<!-- 实现方式1 :使用push-->
function flatten1(arr){
    const res=[];
    arr.forEach(item=>{
        if(Arrery.isArrery(item)){
            item.forEach(i=>res.push(i));
        }else{
            res.push(item);
        }
    })
    return res;
}

<!-- 使用方式2:使用concat -->
function flatten2(arr){
    let res=[];
    arr.forEach(item=>{
        res=res.concat(item);
    })
    return res;
}

彻底扁平化(需使用递归)

<!-- 实现方式1 :使用push-->
function flattenDeep1(arr){
    const res=[];
    arr.forEach(item=>{
        if(Arrery.isArrery(item)){
            const flatItem=flattenDeep1(item);
            flatItem.forEach(i=>res.push(i));
        }else{
            res.push(item);
        }
    })
    return res;
}

<!-- 使用方式2:使用concat -->
function flattenDeep2(arr){
    let res=[];
    arr.forEach(item=>{
        if(Arrery.isArrery(item)){
            const flatItem=flattenDeep1(item);
           res = res.concat(flatItem);
        }else{
           res = res.concat(item);
        }
    })
    return res;
}

手写一个 getType 函数,获取详细的数据类型;

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

new 一个对象发生了什么?手写代码表示;

new 一个对象的过程:

创建一个空对象 obj,继承构造函数的原型;

执行构造函数(将 obj 作为 this);

返回 obj;

function customNew(constructor,...args){
    //创建一个空对象 obj,继承构造函数的原型;
    const obj=Object.create(constructor.prototype);
    //执行构造函数(将 obj 作为 this);
    constructor.apply(obj,args);
    //返回 obj;
    return obj;
}

Object.create 和{}有什么区别:

{}创建空对象,原型指向 Object.prototype;

Object.create 创建空对象,原型指向传入的参数;

深度优先遍历一个 DOM 树

//节点访问
function visitNode(n){
    if(n instanceof Comment){
        //注释
        console.info("Comment node----",n.textContent);
    }

     if(n instanceof Text){
        //文本
        const t = n.textContent?.trim()
        if(t){
        console.info("Text node----",t);
        }
    }

    if(n instanceof HTMLElement){
        //element
        console.info("Element node---",`<${n.tagName.toLowerCase()}>`)
    }
}

// 深度优先遍历(递归)

function depthFirstTraverse1(root){
    visitNode(root);
    const childNodes=root.childNodes;
    if(childNodes){
        childNodes.forEach(item=>depthFirstTraverse1(item))
    }
}

// 深度优先遍历(栈)
function depthFirstTraverse2(root){
    const stack=[];
    stack.push(root);
    while(stack.length>0){
        const curNode = stack.pop();
        if(curNode==null) break;
        visitNode(curNode);

        const childNodes=curNode.childNodes;
        if(childNodes.length){
            Arrery.from(childNodes).reverse().forEach(child=>stack.push(child))
        }
    }
}

广度优先遍历一个 DOM 树

function breadthFirstTracerse(root){
    let queue=[];
    queue.unShift(root);
    while(queue.length>0){
        let curNode = queue.pop();
        if(curNode==null) break;
        visitNode(curNode);
        //子节点入队
        const childNodes =curNode.childNode;
        if(childNodes.length){
            childNodes.forEach(child=>queue.unShift(child))
        }
    }
}

手写 LazyMan

class LazMan {
    private tasks=[];//任务列表
    constructor(name){
        this.name=name;
        setTimeout(()=>this.next());
    }

    //执行下一个任务
    privete next(){
        const task=this.tasks.shift();//取出任务列表第一个执行
        if(task) task();
    }


    eat(food){
        let tast=()=>{
            console.info(`${this.name}吃了${food}`);
            this.next();
        }
        this.tasks.push(tast);
        return this;//链式调用
    }

    sleep(seconds){
        let task=()=>{
            setTimeout(()=>{
                consle.info(`${this.name}睡了${seconds}`);
                this.next();
            },seconds*1000)
        }
        this.tasks.push(tast);
        return this;//链式调用
    }
}

手写 curry 函数,实现函数柯里化

function curry(fn){
    const fnArgsLength=fn.length;//获取函数的参数长度
    let args=[];
    function calc(...newArgs){
        args=[            ...args,            ...newArgs,        ];//累积参数
        if(args.length<fnArgsLength){
            return calc;//参数不够返回函数;
        }else{
            return fn.apply(this,args.slice(0,fnArgsLength));
        }
    }

    return calc;
}

function add(a,b,c){
    return a+b+c;
}

let curruAdd=curry(add);
res=curruAdd(10)(20)(30);

instanceof 原理是什么,请用代码表示;

function myInstanceof(instance,origin){
    if(instance==null) return false;//null,undefined

    const type =type instance;
    if(type!=='object'&&type!=='function'){
        return false;//值类型instanceof返回false
    }

    let tempInstance=instance;
    while(tempInstance){
        if(tempInstance.__proto__===origin.prototype){
            return true;
        }
        tempInstance=tempInstance.__proto__;//未匹配上,顺着原型链继续向上找;
    }
    return false;
}

手写 bind 函数

返回一个新的函数,但是不会执行;

传入的是 this 和参数

参数可以绑定一部分,也可以全部绑定;

Function.prototype.customBind=function(context,...bindArgs){
    //context 是bind传入的this;
    //bindArgs是bind传入的各个参数;

    const self=this;
    //返回一个函数
    return function (...args){
        //拼接参数
        const newArgs=bindArgs.contat(args);
        return self.apply(this,newArgs)
    }
}

手写函数 call 和 apply

bind 返回一个新函数(不执行),call 和 apply 会立即执行函数;

绑定 this;

传入执行参数;

//call
Function.prototype.customCall=function(context,...args){
    if(context==null) context=globalThis;//window
    if(typeof context !== 'objext') context = new Object(context);//值类型,变换对象类型

    const fnKey =Symbol();//不会出现重复属性;
    context[fnKey]=this;//this 就是当前函数,把当前函数添加为传入this的属性方法;

    const res = context[fnKey](...args) //绑定this,执行函数

    delete context[fnKey]; //清理掉fn ,防止污染

    return res;

}

//apply 区别为传入的参数为数组
Function.prototype.customApply=function(context,args=[]){
    if(context==null) context=globalThis;//window
    if(typeof context !== 'objext') context = new Object(context);//值类型,变换对象类型

    const fnKey =Symbol();//不会出现重复属性;
    context[fnKey]=this;//this 就是当前函数,把当前函数添加为传入this的属性方法;

    const res = context[fnKey](...args) //绑定this,执行函数

    delete context[fnKey]; //清理掉fn ,防止污染

    return res;

}

手写 EventBus 自定义事件

*on once:注册函数,存储起来;(on 绑定的事件可以连续执行,除非 off;once 绑定的函数 emit 一次即删除,也可以未执行而被 off;)

*emit:找到对应的函数存储起来;

*off:找对对应的函数,从对象中删除;

class EventBus {
    constructor(){
        this.evens={};
        /*evens
        {
            key1:[
                {fn:fn1,isOnce:false},
                {fn:fn2,isOnce:true},
            ],
             key2:[
                {fn:fn1,isOnce:false},
                {fn:fn2,isOnce:true},
            ],
        }
        */
    }

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

        event[type].push({ fn,isOnce });
    }

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

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

    emit(type,...args){
        const fnList = this.events[type];
        this.events[type]=fnList.filter(item=>{
            const {fn,isOnce}=item;
            fn(...args);
            if(!isOnce) return true;
            return false;
        })
    }
}

用 JS 实现一个 LRU 缓存

LRU 缓存:Least Recentiy Used 最近使用,如果内存优先,只缓存最近使用的,删除"沉水"数据,核心 API : get 和 set;

//使用 Map 实现:数据结构为哈希表,并且有序,可排列,则使用Map实现;
class LRUCache{
    private length=0;
    private data=new Map();

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

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

        if(data.size>this.length){
            const delKey=this.data.keys().next().value;//获取最老的数据
            this.data.delete(delKey);
        }
    }

    get(key){
        if(!this.data.has(key)) return null;
        //如果数据存在,先删除原有的,再更新成最新的数据
        const value=this.data.get(value);
        this.data.delete(key);
        this.data.set(key,value);
        return value;
    }
}

手写 JS 深拷贝,考虑各种数据类型和循环引用;

//只考虑简单的数组和对象;
function cloneDeep(obj){
    if(typeof obj !=='object' || obj ==null) return obj;

    let result;
    if(obj instanceof Arrery){
        result=[];
    }else{
        result={};
    }

    for(let key in obj){
        if(obj.hasOwnProperty(key)){
            result[key] = cloneDeep(obj[key]) //递归调用
        }
    }
    return result;
}
//考虑各种数据类型和循环引用;
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))
    }

    //objec
    for(const key in obj){
        const val = obj[key];
        const val1 = cloneDeep(val,map);
        target [key] = val1;
    }
    return target;
}