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个单位的雨水(蓝色部分表示雨水)。
示例图:
示例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;
}