面试题合集(一)

246 阅读5分钟

1.你了解浏览器的事件循环吗?

1.1 为什么js在浏览器中有事件循环的机制?

1.JS是单线程的,负责解析运行JavaScript脚本;和浏览器的GUI渲染线程互斥,如果JS运行耗时过长会导致页面阻塞。

2.event loop是计算机系统的一种运行机制,js通过这种机制来解决单线程运行带来的问题; 简单说,就是在程序中设置两个线程:一个负责程序本身的运行,称为'主线程';另一个负责主线程与其他线程(主要是各种I/O操作)的通信,被称为'Event Loop线程'(可以译为:消息线程)

1.2 你知道事件循环中的任务分为哪两种?

宏任务:整体的代码块,setTimeout,setInterval,I/O操作

微任务:new Promise().then,MutaionObserver(前端的回溯)

1.3 为什么要引入微任务的概念,只有宏任务可以吗?

宏任务:先进先出的原则执行。

1.4 Node中的事件循环和浏览器中的事件循环有什么区别?

宏任务的执行顺序:

1.timer定时器:执行已经安排的setTimeout 和setInterval的回调函数;

2.pending callback 待定回调:执行延迟到下一个循环迭代的I/O回调;

3.idle,prepare:仅系统内部使用;

4.poll:检索新的I/O事件,执行与I/O相关的回调;

5.check:执行setImmediate()回调函数;

6.close callback:socket.on('close',()=>{})

微任务和宏任务在node中的执行顺序:

不同的node版本执行顺序也不一样

Node v10及以前:

1.执行完一个阶段中的所有任务

2.执行nextTick队列里的内容

3.执行完微任务队列的内容

Node v10后: 与浏览器的行为统一。

1.5 来看一些事件循环的题目

1. 难度(easy)
async function async1(){
    console.log('async1 start');
    await async2();
    console.log('async1 end');
}
async function async2(){
    console.log('async2');
}
console.log('script start');
setTimeout(function(){
    console.log('setTimeout');
},0)
async1();
new Promise(function(resolve){
    console.log('promise1');
    resolve();
}).then(function(){
    console.log('promise2');
})
console.log('script end');
执行以上结果为:
console.log('script start');
console.log('async1 start');
console.log('async2');
console.log('promise1');
console.log('script end');
console.log('async1 end');
console.log('promise2');
console.log('setTimeout');
await async2() 相当于=>  new Promise( resolve => { resolve() } );
2.难度(middle)
console.log('start');
setTimeout(()=>{
    console.log('alie1');
    Promise.resolve().then(()=>{
        console.log('alie2');
    })
},0)
new Promise(function(resolve){
    console.log('alie3');
    setTimeout(()=>{
        console.log('alie4');
        resolve('alie5');
    },0)
}).then((res)=>{
    console.log('alie6');
    setTimeout(()=>{
        console.log(res);
    },0)
})
执行以上结果为:
console.log('start');
console.log('alie3');
console.log('alie1');
console.log('alie2');
console.log('alie4');
console.log('alie6');
console.log('alie5');
3.难度(high)
const p=function(){
    return new Promise((resolve,reject)=>{
        const p1=new Promise((resolve,reject)=>{
            setTimeout(()=>{
                resolve(1);
            },0);
            resolve(2);
        })
        p1.then((res)=>{
            console.log(res);
        })
        console.log(3);
        resolve(4);
    })
}
p().then(res=>{
    console.log(res);
})
console.log('end');
执行以上结果为:
console.log('3');
console.log('end');
console.log('2');
console.log('4');

2.事件的捕获和冒泡机制你了解多少?

1.基本概念?

捕获:自顶向下
冒泡:自底向上

2.window.addEventListener 监听的是什么阶段的事件?

//其中的boolean为false则是冒泡阶段,为true则是捕获阶段;
window.addEventListener('click',()=>{},boolean)

3.平常哪些场景用到了这个机制呢?

事件委托,可以应用于商品列表: 每个商品的点击绑定在所有商品的父元素上,通过捕获阶段去触发商品的点击事件。

4.一个历史界面,上面有若干按钮的点击逻辑,每个按钮都有自己的click事件。

新需求:给每一个访问的用户添加了一个属性,banned = true,此用户点击页面上的任何按钮或者元素,都不可响应原来的函数。而是直接alert提示,你被封禁了。

window.addEventListener('click',function(){
    if(banned === true){
        alert('你被封禁了')
    }
},true)

3.工作中用过防抖和节流吗?

1.基本概念?

防抖:当你持续触发事件的时候,一定时间段内没有再触发事件,事件处理函数才会执行一次。

节流:当你持续触发事件的时候,保证一定时间段内只触发一次事件处理函数。

2.分别适合用在什么场景?

节流:resize scroll 防抖:input

3.手写一个节流函数?

时间戳写法,第一次调用函数立即执行
*fn:需要执行的函数
*interval:间隔时间
function throttle(fn,interval){
    let last=0;
    return function(){
        let now=Date.now();
        if(last+interval<now){
            last=now;
            fn.apply(this,arguments);
        }
    }
}
定时器写法,第一次调用函数不会立即执行,但是最后一次点击延迟执行函数
*fn:需要执行的函数
*interval:间隔时间
function throttle(fn,interval){
    let timer=null;
    return function(){
        if(!timer){
           timer=setTimeout(()=>{
               fn.apply(this,arguments);
               timer=null;
           },interval)
        }
    }
}
定时器时间戳合并写法
*fn:需要执行的函数
*interval:间隔时间
function throttle(fn,delay){
    let timer=null;
    let startTime=Date.now();
    return function(){
       let curTime=Date.now();
       let remaining=delay - (curTime - startTime);
       let context=this;
       let args=arguments;
       clearTimeout(timer);
       if(remainning<=0){
           fn.apply(context,args);
           startTime=Date.now();
       }else{
           timer=setTimeout(fn,remainning);
       }
    }
}

4.你了解Promise吗?平时用的多吗?

1.Promise.all 你知道有什么特性吗?

对传入的数组每一项进行执行,如果每一项都成功返回,则在最后一项执行完返回resolve,如果有一项失败,则执行返回reject。如果有失败项被捕获,数组其他项也会执行。

手写promise.all()的实现函数,并返回promise;

function PromiseAll(promiseArr){
    //返回promise函数
    return new Promise((resolve,reject)=>{
        //判断传入是否为一个数组
        if(!Array.isArray(promiseArr)){
            return reject(throw Error('请传入一个数组!'));
        }
        let len=promiseArr.length;
        let result=[];//用来保存执行结果
        let counter=0;//累计执行结果的条数
        for(let i=0;i<len;i++){
            //拿到的promiseArr[i],有可能不是promise,需要使用Promise.resolve()转为promise
            Promise.resolve(promiseArr[i]).then(res=>{
                //result.push(res) 会不按照顺序添加结果
                result[i]=res;
                counter++;
                if(counter===len){
                    resolve(result);
                }
            })
        }
    })
}

2.promise如何做cache?

比如一些promise的结果是固定的,你去请求一个接口,这个接口的请求参数是常量,那你在每一个页面都要调用,如果你没有全局状态的管理,也可以做一个promise的缓存!

//装饰器写法,需要安装插件:
const cacheMap=new Map();
function enableCache(target,name,descriptor){
    const val=descriptor.value;
    descriptor.value=async function(...args){
        const cacheKey=name+JSON.stringify(args);
        if(!cacheMap.get(cacheKey)){
            const cacheValue=Promise.resolve(val.apply(this,args)).catch(_=>{
                cacheMap.set(cacheKey,null);
            })
            cacheMap.set(cacheKey,cacheValue);
        }
        return cacheMap.get(cacheKey);
    }
}
//类名中使用装饰器
class PromiseClass{
    @enableCache
    static async getInfo(){}
}
//调用
PromiseClass.getInfo();
...

5.算法

接雨水

给定n个非负整数表示每个宽度为1的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
示例1:
输入:height=[0,1,0,2,1,0,1,3,2,1,2,1]
输出:6
解释:上面是由数组height表示的高度图,在这种情况下,可以接6个单位的雨水(蓝色部分表示雨水)。 示例图:

无标题.png

示例2: 输入:height=[4,2,0,3,2,5]
输出:9

思路

仅对于位置i,能装下多少水?(与左边最高的柱子和右边最高的柱子有关)
暴力解法:
function trap(array=[]){
    if(!array.length){
        return 0;
    }
    const len=array.length;
    let res=0;
    for(let i=1;i<len-1;i++){
        let left_max=0,right_max=0;
        for(let j=i;j<len;j++){
            //取当前水位i右侧最高的柱子
            right_max=Math.max(right_max,array[j]);
        }
        for(let j=i;j>=0;j--){
            //取当前水位i右侧最高的柱子
            left_max=Math.max(left_max,array[j]);
        }
        //取出循环完左侧最大柱子和右侧最大柱子,取小的就是最大容量,再减去当前柱子高度就是水容积;
        res+=Math.min(left_max,right_max) - array[i];
    }
    return res;
}
暴力优化:
复杂度:O(N)
function trap(array=[]){
    if(!array.length){
        return 0;
    }
    const len=array.length;
    let res=0;
    let left_arr=new Array(len);
    let right_arr=new Array(len);
    left_arr[0]=array[0];
    right_arr[len-1]=array[len-1];
    for(let i=1;i<len;i++){
        left_arr[i]=Math.max(array[i],left_arr[i-1])
    }
    for(let i=len-2;i>=0;i--){
        right_arr[i]=Math.max(array[i],left_arr[i+1])
    }
    for(let i=1;i<len-1;i++){
        res+=Math.min(left_arr[i],right_arr[i]) - array[i];
    }
    return res;
}
双指针:
function trap(array=[]){
    if(!array.length){
        return 0;
    }
    const len=array.length;
    let res=0;
    let left=0;right=len-1;
    let left_max=array[0],right_max=array[len-1];
    while(left<=right){
        left_max=Math.max(left_max,array[left]);
        right_max=Math.max(right_max,array[right]);
        if(left_max<right_max){
            res+=left_max-array[left]
            left++;
        }else{
            res+=right_max-array[right]
            right--;
        }
    }
    return res;
}