最近刚面试了一家互联网公司的中级前端开发工程师。好家伙,一面上来直接开始手写题,考算法什么的。
特此记录一下考题。看能不能帮助到大家,有些题忘记了,记录个大概吧。
--手写题--
1.let,var区别
2. event loop执行结果
3. 算法题
4. promise考察
5. 算法题,判断有效括号
--技术题--
6. 说一下diff算法
7. 输入url到页面显示的过程
9. webpack工作过程,以及使用过webpack的哪些东西
最后
for(let i = 0; i<5; i++) {
setTimeout(() => console.log(i),0)
}
问执行结果,以及let换成var的执行结果
我的回答:
我当时一看就是靠let块级作用域的,我一般for循环的话也喜欢用let,所以基本是答对了,但是却输错了循环次数,无语子。。。
正确答案:
let: 0 1 2 3 4
var: 5 5 5 5 5
解析:
var声明的是全局变量,然后的话setTimeout是异步函数,等循环完i变成了5,所以输出了5个5;
而let是块级的,每次循环有自己的局部作用域,因此输出0-4
题记不清了,大概是下面这两种,我大概写了一下
setTimeout(function() {
console.log('1')
new Promise(function() {
console.log('2')
}).then(
console.log('3')
)
},0)
new Promise(function() {
console.log('4')
}).then(
console.log('5')
)
setTimeout(function() {
new Promise(function() {
console.log('6')
}).then(
console.log('7')
)
},0)
正确答案:
script start 2 3 script end 1 4
setTimeout(function() {
console.log('1')
new Promise(function() {
console.log('2')
}).then(
console.log('3')
)
},0)
new Promise(function() {
console.log('4')
}).then(
console.log('5')
)
setTimeout(function() {
new Promise(function() {
console.log('6')
})
},0)
正确答案:
4 5 1 2 3 6
我的回答:
第一个回答挺顺利的,然后第二个卡住了,当时有点蒙了,第一次写结果想成了执行完所有宏任务再去执行微任务队列。后来反应过来了,是执行一个宏任务,然后执行所有微任务。
所以当时就卡在了最后是1263 还是 1236 ,还好最后改正了
有一个函数add:
执行 add(1)(1+2+3+4) ,结果为11
执行 add(2)(1+2+3+4) ,结果为12
执行 add(2)(1+2+3+4+1+1) ,结果为14
请写出add这个函数
我的回答:
首先的话,我先去观察这个函数的特点。
观察片刻,发现是第二次输入的值加上第一次输入的值
然后的话,该函数能连续执行,肯定add函数内部要返回一个方法
所以我的答案:
第一次写出来:
function add(num) {
let result
return function(num,...rest){
let result = rest.reduce((a,b) => a+b)+n
return result
}
}
第一次的答案我按照我的思路,后面的值加前面的值,所以后面值接收用...rest剩余参数,因为第二次传参数量不一定嘛,但是面试官提示,第二次调用没有传num。所以我才反应过来,第二次只是传第二次的值。
然后我立马想到用arguments代替rest参数,然后的话,第一个传入的数字在第一层函数里声明下,这要就构成了闭包,第二层函数可以直接用了~ Nice
function add(num) {
let n = num
return function(){
let arr = Array.from(arguments)
let result = arr.reduce((a,b) => a+b)+n
return result
}
}
var a = Promise.resolve()
var b = Promise.resolve('foo')
var c = Promise.resolve(() => null)
var d = Promise.resolve(() => undefined)
var e = Promise.resolve(() => 'foo')
var f = Promise.resolve(() => Promise.resolve('foo'))
var g = Promise.resolve(() => Promise.reject('err'))
var h = Promise.resolve(() => new Promise(() => {
Error('err')
}))
console.log(a,b,c,d,e,f,g,h)
请写出a,b,c,d,e,f,g,h的值
我的回答:
我看到这题,直接一脸懵逼,瞎说了一通,一半答错了
正确答案:
Promise {
Promise {
Promise {
Promise {
Promise {
Promise {
Promise {
解析:
MDN详解
Promise.resolve()
方法返回一个 Promise使用给定值解析的对象。如果该值是一个承诺,则返回该承诺;如果值是一个 thenable(即有一个 "then" method),返回的 promise 将“跟随”那个 thenable,采用它的最终状态;否则返回的 Promise 将用该值实现。这个函数将嵌套的类似 promise 的对象层(例如,解析为解析为某事物的 promise)扁平化为单层。
有时需要将现有对象转换为promise对象,Promise.resolve()方法就起到了这个作用。
Promise.resolve('foo')
等价于
new Promise(resolve => resolve('foo'))
第一种:不带任何参数
setTimeout(function () {
console.log('three');
}, 0);
Promise.resolve().then(function () {
console.log('two');
});
console.log('one');
// one two three
相当于一个resolve状态的promise对象
第二种:普通变量或者普通对象
const p = Promise.resolve('Hello');
p.then(function (s){
console.log(s)
});
// Hello
相当于resolve状态的promise对象
第三种:参数是一个 Promise 实例
如果参数是 Promise 实例,那么Promise.resolve将不做任何修改、原封不动地返回这个实例。
第四种:参数是一个thenable对象
//thenable对象指的是具有then方法的对象,比如下面这个对象
let thenable = {
then: function(resolve, reject) {
resolve(42);
}
};
//Promise.resolve方法会将这个对象转为 Promise 对象,然后就立即执行thenable对象的then方法。
let thenable = {
then: function(resolve, reject) {
resolve(42);
}
};
let p1 = Promise.resolve(thenable);
p1.then(function(value) {
console.log(value); // 42
});
//thenable对象的then方法执行后,对象p1的状态就变为resolved,
//从而立即执行最后那个then方法指定的回调函数,输出 42
写一个方法,输入一个只有括号的字符串,(){}[] [(),{}] {[()]} 这些都会返回true
([)] (}) 这种都会返回false
我的回答:
我一看这个题,心想:好家伙,我在力扣上刷到过,稳了!再一想,忘了怎么做了。。。焯!
第一次作答:
我的思路是先判断是不是空字符串和偶数,排除特殊情况。
然后遍历这个字符串,对元素的六种情况 ( , ),{ , } , [ , ] ,分别做判断,比如如果是左小括号,它的右侧必须是右小括号。
but!面试官提示,只能判断 (){}[] 这种情况,那 {[()]}这种怎么办呢?
第二次作答:
我反手想到另一种解决办法,双指针,遍历两层字符串,第一层正向,第二层反向,然后比如第一层如果是左小括号,第二层就必须是右小括号。
but!面试官提示,这只能判断{[()]}这种情况了
第三次作答:
焯!!什么鬼,我蒙圈了。突然我灵机一动,想到挨个去找到匹配成功的括号,然后将匹配成功的括号从字符串中删除,最后看这个字符串剩下什么。
就在这时,面试官也提示我,可以想想换一种数据结构,考虑一下用栈。其实我的想法基本也是和栈差不离,只不过不会。。。
正确答案:
var isValid = function(s) {
const n = s.length;
if (n % 2 === 1) {
return false;
}
const pairs = new Map([
[')', '('],
[']', '['],
['}', '{']
]);
const stk = [];
for (let ch of s){
if (pairs.has(ch)) {
if (!stk.length || stk[stk.length - 1] !== pairs.get(ch)) {
return false;
}
stk.pop();
}
else {
stk.push(ch);
}
};
return !stk.length;
};
到这里,手写题就结束了,然后问了一些技术题:
我的回答:
1. vue的diff算法是从两侧向中间节点去对比的。并且使用了双指针,边对比边更新。而react的diff算法是从左到右去对比的,对比时把改变的节点生成patch树,对比完才去打补丁。
2. diff算法一般要指定节点key,也就是唯一值,这样才能在节点进行删除或者排序操作后,保证diff对比的准确性。
3. vue3的diff算法相比vue2做了优化,它会在编译阶段判断每个节点是否是动态节点,通过看节点上有没有像v-on,:class等这种符号,然后收集动态节点生成block tree,在dom发生变化时,只会去比对block tree。因为vue3对静态节点做了静态提升,所以静态节点只会渲染一次。
4. vue3的diff算法还再用了patchFlag标记动态节点,更高效的判断修改了哪些东西。
1. url没输入完成时,查找书签,历史记录等,可以智能提示,补全地址
2. 因为输入的是域名,所以要先解析出域名映射的ip地址。所以要先进行DNS解析,首先去看浏览器缓存有没有,没有的话请求本地DNS服务器,也就是本地运营商,还没有再请求根DNS服务器,然后再顶级域名服务器,最后权威域名服务器。知道找到为止。
3. 解析出ip地址后,进行tcp连接
4. 浏览器网络进程发起请求,三次握手请求数据,拿到数据后,四次挥手,释放tcp连接
5. 浏览器浏览器进程准备开始渲染,准备好后ipc进程通信通知渲染进程开始渲染
6. 渲染进程开始渲染,先解析html,生成dom树,然后解析css规则,生成css树,然后结合生成render树,这时的render树会去掉head标签以及dispaly:none的节点,所以和dom树结构不完全相同。最后布局,绘制。
工作过程的话,我可能当时没理解对,说了点ast抽象语法树的东西。实际应该是说这个:
compile
开始编译make
从入口点分析模块及其依赖的模块,创建这些模块对象build-module
构建模块after-compile
完成构建seal
封装构建结果emit
把各个chunk输出到结果文件after-emit
完成输出然后使用我说的这些:
1. 配置svg啥的loader
2. 开sourcemap
3. 开gzip压缩
4. 配置跨域
5. 分包
6. 安装插件
7. 第三方库按需加载
如果哪里有不对或者补充的,欢迎大家指正或补充!