【面试】中级前端面试题记录及答案总结

前言

最近刚面试了一家互联网公司的中级前端开发工程师。好家伙,一面上来直接开始手写题,考算法什么的。

特此记录一下考题。看能不能帮助到大家,有些题忘记了,记录个大概吧。

目录

--手写题--

1.let,var区别

2. event loop执行结果

3. 算法题

4. promise考察

5. 算法题,判断有效括号

--技术题--

6. 说一下diff算法

7. 输入url到页面显示的过程

9. webpack工作过程,以及使用过webpack的哪些东西

最后


1.let,var区别

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

2. event loop执行结果

题记不清了,大概是下面这两种,我大概写了一下

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  ,还好最后改正了

3. 算法题

有一个函数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
  }
}

4. promise考察

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 {<fulfilled>: undefined}

Promise {<fulfilled>: 'foo'}

Promise {<fulfilled>: f}

Promise {<fulfilled>: f}

Promise {<fulfilled>: f}

Promise {<fulfilled>: f}

Promise {<fulfilled>: f}

解析:

MDN详解

Promise.resolve()方法返回一个 Promise使用给定值解析的对象。如果该值是一个承诺,则返回该承诺;如果值是一个 thenable(即有一个 "then" method),返回的 promise 将“跟随”那个 thenable,采用它的最终状态;否则返回的 Promise 将用该值实现。这个函数将嵌套的类似 promise 的对象层(例如,解析为解析为某事物的 promise)扁平化为单层。

有时需要将现有对象转换为promise对象,Promise.resolve()方法就起到了这个作用。

Promise.resolve('foo')

等价于

new Promise(resolve => resolve('foo'))

Promise.resolve()共有四种参数

第一种:不带任何参数

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

5. 算法题,判断有效括号

写一个方法,输入一个只有括号的字符串,(){}[]      [(),{}]     {[()]} 这些都会返回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;
};


到这里,手写题就结束了,然后问了一些技术题:

6. 说一下diff算法

我的回答:

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标记动态节点,更高效的判断修改了哪些东西。

7. 输入url到页面显示的过程

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树结构不完全相同。最后布局,绘制。

8. webpack工作过程,以及使用过webpack的哪些东西

工作过程的话,我可能当时没理解对,说了点ast抽象语法树的东西。实际应该是说这个:

  • compile 开始编译
  • make 从入口点分析模块及其依赖的模块,创建这些模块对象
  • build-module 构建模块
  • after-compile 完成构建
  • seal 封装构建结果
  • emit 把各个chunk输出到结果文件
  • after-emit 完成输出

然后使用我说的这些:

1. 配置svg啥的loader

2. 开sourcemap

3. 开gzip压缩

4. 配置跨域

5. 分包

6. 安装插件

7. 第三方库按需加载

最后

如果哪里有不对或者补充的,欢迎大家指正或补充!

猜你喜欢

转载自blog.csdn.net/qq_38974163/article/details/124218610