1 说一下工作中解决过的比较困难的问题,说一下自己项目中比较有亮点的地方
注意点:
1 面试官要看一下你解决问题的能力
2 工作中做好笔记 遇到问题怎么解决,将问题集中积累,面试中的时候可以做到很好的回答
2 你了解浏览器的事件循环吗
2.1 为什么浏览器中有事件循环的机制?
大体答案: 比如js是单线程,如果两个线程操作dom,一个线程删除dom节点,另外一个是操作dom节点,那么就会出现冲突。浏览器就会无法正确渲染出我们想要的结果。
是如果做到非阻塞的?? 执行代码的时候会有些异步的机制怎么处理??
通过event loop 来实现了非阻塞的事件
2.2 了解事件循环中有两种任务吗
宏任务:整体的代码(main),setTimeout,setInterval, I/O操作
微任务:new Promise().then,MutainObserver(前端的回溯)
2.3 为什么要引入微任务的概念,只要宏任务可以么?
宏任务 保持的是先进先出的原则执行。但是如果说有个高优先级的任务,依照宏任务的原则肯定是后执行的,所以这个时候就出现了微任务。
在整体的宏任务中执行完,会将遇到到微任务添加到队列中去,如果微任务中有其他的微任务,会依次执行完,然后再次执行下一个宏任务。
2.4 了解nodejs的事件循环吗?和浏览器中的事件循环有什么区别?
宏任务的执行顺序:
1 timers定时器: 执行已经安排的setTimeout和setInterval的回调函数
// 回调函数开始
()=> {},
// 回调结束
1000)
2 pending callback 待定回调:延迟到下一个循环迭代的I/O回调函数
3 idle, prepare:仅系统内部使用。
4 poll: 检索新的I/O事件,执行与I/O相关的回调。
5 check:执行setImmediate()的回调函数
6 close callbacks: socket.on('close', () => {}) socket回调函数的关闭
微任务和宏任务在node中的执行顺序:是区别与node版本的,不同版本执行顺序也会不一样
node v10 版本为界限:
node v10以前:
1 执行完一个阶段中所以任务
2 执行nextTick队列中的内容
3 执行完微任务队列的内容
node v10以后,执行的宏任务和微任务的执行顺序和浏览器统一
事件循环的笔试题:
思考:
3 事件的捕获和冒泡机制你了解多少??
3.1 基本概念??
1 捕获是从window一直到目标元素,是自顶向下 2 冒泡是从目标元素到window,是自底向上
3.2 window.addEventListener('click'), 监听的是什么时候的事件???
1 首先要理解的是,监听的不外两种一个是捕获一个是冒泡,但是window.addEventListener这个除了事件名,回调函数,还有第三个参数(true/false),来决定是捕获还是冒泡,默认不传的时候 是false,false代表冒泡但是
3.3 平时有哪些场景会用到这个机制???
原始的写法:
缺点是: 会在每个元素上绑定一个事件
优化:事件委托,只在父元素上绑定事件
首先 liList并不是一个真正数组,而是一个node list dom节点的数组,而想要去调用array上的indexOf的方法,那只能去通过call改变this,来达到调用数组原型链上的各种方法
3.4 一个历史页面(很多人开发),上面有若干按钮的点击逻辑,每个按钮都有自己的click事件
新需求:给每个访问的用户添加一个属性,banned=true,此用户点击页面上的任何按钮或者元素都不可以响应原来的函数,而是直接alert提示,你被封禁了。
解决方法:直接在window或者body上直接捕获,进行拦截
window.addEventListener('click', (e) => {
if(banned === true){
e.stopProgagtion()
}
}, true)
3.4.1 事件捕获和冒泡的顺序
4 工作中用过防抖和节流么?
4.1 基本概念?
防抖:当你持续触发事件的时候,一定时间段内没有再触发事件,事件处理函数才会执行一次。是一直往后顺延的,比如一个点击的事件,只要在不点击的时间,比如1s 没有点击,才会触发。
节流:当你持续触发事件的时候,保证一定时间段内,只调用一次处理函数。固定时间段,固定时间间隔是触发事件。
4.2 分别适合在什么时候???
节流:resize(屏幕大小改变) scroll(滚动)这两个是一定要执行,频率会非常的高,所有给个延迟执行的时间
防抖: input (有可能输入的时候有联动,如果输一个就调用一次接口,那么会非常的耗性能)
4.3 手写节流和防抖函数
防抖:
function debounce(fn, wait = 1000) {
let timer = null;
return function debounced(...args) {
// 重置计时器
if (timer) clearTimeout(timer);
// 新计时器
timer = setTimeout(() => {
fn.apply(this, ...args);
timer = null;
}, wait);
};
}
有时候我们会要求函数在第一次触发立即执行,我们来为它添加个参数。
function debounce(fn, wait = 1000, immediate = false) {
let timer = null;
return function debounced(...args) {
// 重置计时器
if (timer) clearTimeout(timer);
// 首次立即执行
if (immediate && !timer) {
fn.apply(this, ...args);
timer = setTimeout(() => {
timer = null;
}, wait);
return;
}
// 新计时器
timer = setTimeout(() => {
fn.apply(this, ...args);
timer = null;
}, wait);
};
}
为其添加取消的功能
function debounce(fn, wait = 1000, immediate = false) {
let timer = null;
function debounced(...args) {
// 重置计时器
if (timer) clearTimeout(timer);
// 首次立即执行
if (immediate && !timer) {
fn.apply(this, ...args);
timer = setTimeout(() => {
timer = null;
}, wait);
return;
}
// 新计时器
timer = setTimeout(() => {
fn.apply(this, ...args);
timer = null;
}, wait);
}
debounced.cancel = () => {
clearTimeout(timer);
timer = null;
};
return debounced;
节流:
// 时间戳写法 第一次是立即执行
function throttle(fn, interval){
let last = 0;
return function(){
let now = Date.now()
if(now-last >= interval){
last = now;
fn.apply(this.arguments)
}
}
}
function handle(){
console.log(Math.random())
}
const throttleHandle = throttle(handle, 1000)
throttleHandle()
// 定时器的写法 第一次不立即执行的方法
function throttle(fn, interval){
let timer = null
return function(){
let context = this;
let ars = arguments;
if(!timer){
timer = setTimeout(function(){
fn.apply(context, ars)
timer = null;
}, interval)
}
}
}
function handle(){
console.log(Math.random())
}
const throttleHandle = throttle(handle, 1000)
throttleHandle()
缺点: 最后一次触发 也要去等 能不能是最后一次触发不需要等,直接立即执行
## 节流优化
function throttle(fn, delay){
// 设置定时器开关
let timer = null;
// 开始时间
let startTime = Date.now()
return function(){
// 当前时间
let current = Date.now()
// 剩余时间,从上一次执行到现在 还有多久需要执行下一次
let remainning = delay - (current - startTime);
// 保持this
let context = this;
// 保持匿名参数
let args = arguments;
// 每次都把定时器关闭
clearTimeout(timer)
// 如果已经没有剩余时间了 就立即执行下一次事件
if(remainning <= 0){
fn.apply(context, args)
startTime = Date.now()
}else{
// 如果还有剩余时间 生成timer 在延迟剩余时间后去执行事件
// setTimeout 本身就不是特别精确的延迟 中间在执行微任务的时候 有可能会拖延一些时间
timer = setTimeout(fn, remainning)
}
}
}
function handle(){
console.log(Math.random())
}
const throttleHandle = throttle(handle, 1000)
throttleHandle()
防抖:
节流:
5 你了解promise吗?平时用的多吗?
5.1 Promise.all() 你知道有什么特性么?
特性: promise.all() 中的参数是一个数组,里面元素可以是promis也可以是一个常量,比如数字 1 执行情况是,所有的promise执行完成之后,才会输出执行的数据,如果其中一个promise报错,那么就会返回catch,那么其他的promise还会执行么?会的,promise在实例化的已经执行了,我们平常的是.then() 只是需要去拿到他的结果。其实已经执行完。
手写promise.all()
function PromiseAll (promiseArray){
return new Promise((resolve, reject) => {
// promiseArray 传进来的是一个数组 首要是判断传进来的值对不对
if(!Array.isArray(promiseArray)){
return reject(new Error('传入的参数必须是数组!'))
}
// 用于存输出的值
const res = [];
const promiseNums = promiseArray.length;
// 判断执行的次数是否和传进来的数组是否一直
let counter = 0;
// 循环传进来的值
for(let i=0; i < promiseNums; i++){
// 这一步很重要,首先传来的数组中不仅仅报错promise,还有可能是其他的类型的值
// Promise.resolve()这个特性是 会将所有传来的值都转成promise,这样就省略了判断是不是promise的这个类型
Promise.resolve(promiseNums[i]).then(value => {
counter++;
res[i] = value;
if(counter === promiseNums){
resolve(res)
}
}).catch(e => reject(e))
}
})
}
promise 在实例的时候就应该执行了,那么可以做一个promise的缓存,这样在其他同样调用接口的时候就可以不需要再次去调用接口了---
装饰器
6算法
暴力解法: 时间复杂度 o的二次方 空间复杂度o(1)
时间复杂度 o(n)
空间复杂度 o(n)
双指针法:
7 面试结束了,你有什么要问我们的么?
1 追求的是什么? 钱: 一定要了解业务,更接近页面,要了解业务,去了解公司是怎么运作的,每一个产品需求的背景。前端深度没有后台深,但是前端的广度是很广的
技术:确定一个方向深挖(vue,react,angluarjs)不算。图形学、编辑器、营销页面。看公司需求,适当的了解公认的技术。