前端面试笔记(更新于2021-5-07)

875 阅读15分钟

面试血泪史

不要问我面的是不是架构师。我只是面的低级前端。总结不易点个赞吧。

JS

1. 包装类跟普通类有什么区别?new String() 和String()的区别

答::js 提供了3个包装类 分别是 new String()new Number()new Boolean()。由于基础类型不能添加属性和方法,js的包装类的作用是将基础类型包装成一个对象,这样就可以有属性和方法。

tips:当我们对一些基本数据类型的值去调用属性和方法时,浏览器会临时使用包装类将其转换为对象,然后在调用对象的属性和方法;调用完以后,在将其转换为基本数据类型。

2. promise.then 是怎么实现链式调用的

: 通过重新return 一个new Promise 来实现链式调用

3. 多调用几次bind 比如bind a bind b 最后this指向谁 为啥

答: 永远指向第一次调用bind时传入的上下文,因为bind之后的调用都是绑定在这个上下文上。

4. v8引擎回收机制简述

答: v8垃圾回收主要通过两个策略:

  • 标记清除

  • 引用计数

    标记清除是js最常用的垃圾回收机制。垃圾回收程序运行的时候,会标记内存中存储的所有变量。然后,它会将所有在上下文中的变量,以及被在上下文中的变量引用的变量的标记去掉。在此之后再被加上标记的变量就是待删除的了,原因是任何在上下文中的变量都访问不到它们了。随后垃圾回收程序做一次内存清理,销毁带标记的所有值并收回它们的内存。

    引用计数是对每个值都记录它被引用的次数。声明变量并给它赋一个引用值时,这个值的引用数为 1。如果同一个值又被赋给另一个变量,那么引用数加 1。类似地,如果保存对该值引用的变量被其他值给覆盖了,那么引用数减 1。当一个值的引用数为 0 时,就说明没办法再访问到这个值了,因此可以安全地收回其内存了。垃圾回收程序下次运行的时候就会释放引用数为 0 的值的内存。(以上摘自js红宝书第四版)

5. v8回收算法运行时会阻塞js吗?为什么

答: 会阻塞。

6. 怎么优化垃圾回收机制

答: www.cnblogs.com/chengxs/p/1… 总结就是多用新生代算法

7. 作用域链简述。怎么能获取函数内部变量?

答: 作用域链就是变量向上查找过程。可以通过函数内部return 一个携带函数内部变量的闭包使得外部可以访问函数内部的变量

8. 闭包简述,怎么避免内存泄漏

答: 无论何时声明新函数并将其赋值给变量,都要存储函数定义和闭包,闭包包含在函数创建时作用域中的所有变量,类似于背包,函数定义附带一个小背包,他的包中存储了函数创建时作用域中的所有变量。及时将指针指向null可以避免内存泄漏。

9. class 类可以枚举吗?类 instanceof Function 输出什么?

答: 类的内部所有定义的方法,都是不可枚举的类的数据类型就是函数,类本身就指向构造函数。 代码如下:

class Point {
  constructor(x, y) {
    // ...
  }

  toString() {
    // ...
  }
}

Object.keys(Point.prototype)
// []
Object.getOwnPropertyNames(Point.prototype)
// ["constructor","toString"]
class Fn{}
Fn instanceof Function // true

const a = new Fn()
a instanceof Function // false

webpack

1. webpack 是怎么实现分模块打包的?

答: 可以通过splitChunks 实现。

webpack 中以下三种常见的代码分割方式:

  • 入口起点:使用 entry 配置手动地分离代码。
  • 动态导入:通过模块的内联函数调用来分离代码。
  • 防止重复:使用 splitChunks 去重和分离 chunk。 第一种方式,很简单,只需要在 entry 里配置多个入口即可。

splitChunks 代码拆分

splitChunks: {
    // 表示选择哪些 chunks 进行分割,可选值有:async,initial和all
    chunks: "async",
    // 表示新分离出的chunk必须大于等于minSize,默认为30000,约30kb。
    minSize: 30000,
    // 表示一个模块至少应被minChunks个chunk所包含才能分割。默认为1。
    minChunks: 1,
    // 表示按需加载文件时,并行请求的最大数目。默认为5。
    maxAsyncRequests: 5,
    // 表示加载入口文件时,并行请求的最大数目。默认为3。
    maxInitialRequests: 3,
    // 表示拆分出的chunk的名称连接符。默认为~。如chunk~vendors.js
    automaticNameDelimiter: '~',
    // 设置chunk的文件名。默认为true。当为true时,splitChunks基于chunk和cacheGroups的key自动命名。
    name: true,
    // cacheGroups 下可以可以配置多个组,每个组根据test设置条件,符合test条件的模块,就分配到该组。
    // 模块可以被多个组引用,但最终会根据priority来决定打包到哪个组中。默认将所有来自 
    // node_modules目录的模块打包至vendors组,将两个以上的chunk所共享的模块打包至default组。
    cacheGroups: {
        vendors: {
            test: /[\\/]node_modules[\\/]/,
            priority: -10 // 缓存组优先级
        },
        // 
    default: {
            minChunks: 2,
            priority: -20,
            reuseExistingChunk: true  // 可设置是否重用该chunk
        }
    }
}

通过 cacheGroups,我们可以定义自定义 chunk 组,通过 test 条件对模块进行过滤,符合条件的模块分配到相同的组。

2. webpack4 的tree-shaking是什么?怎么实现的?在什么情况下会失效?为什么?

答: tree-shaking本质是webpack打包时用来舍弃无用的代码。

工作原理: 在ES6以前,我们可以使用CommonJS引入模块:require(),这种引入是动态的,也意味着我们可以基于条件来导入需要的代码

let module
if(true){
    module = require('a')
}else{
    module = require('b')
}

CommonJS规范无法确定在实际运行前需要或者不需要某些模块,所以CommonJS不适合tree-shaking机制

ES6的import语法可以完美使用tree shaking,因为可以在代码不运行的情况下就能分析出不需要的代码。

因为tree shaking只能在静态modules下工作。ECMAScript 6 模块加载是静态的,因此整个依赖树可以被静态地推导出解析语法树。

side effects是指那些当import的时候会执行一些动作,但是不一定会有任何export

tree shaking 不能自动的识别哪些代码属于side effects,因此手动指定这些代码显得非常重要。如果所有代码都不包含副作用,我们就可以简单地将该属性标记为false,来告知 webpack,它可以安全地删除未用到的export导出。

总结: ES6 Module引入进行静态分析,故而编译的时候正确判断到底加载了那些模块。再判断那些模块和变量未被使用或者引用,进而删除对应代码。

另外,webpack中可以在项目package.json文件中,添加一个 “sideEffects” 属性,手动指定由副作用的脚本。

3. env 知道吗?是用来干什么的?项目需要单独安装吗?为什么?

env是nodejs里内置的一个对象,可以利用process.env拿到当前项目运行环境的信息。不需要独立安装,因为是nodejs的内置对象。

4. import 和 require 的区别

答:

  1. CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。

  2. CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。

  3. CommonJs 是单个值导出,ES6 Module可以导出多个

  4. CommonJs 是动态语法可以写在判断里,ES6 Module 静态语法只能写在顶层

  5. CommonJs 的 this 是当前模块,ES6 Module的 this 是 undefined

5. 知道什么是静态分析吗?

答: es modules可以在代码不运行的情况下对代码进行分析,可以知道哪些模块有没有被使用。

6. webpack babel是如何工作的?

答:

    1. 词法解析 。将字符串形式的代码转换为Tokens(令牌),Tokens 可以视作是一些语法片段组成的数组。
    1. 语法解析。把Tokens转换为抽象语法树AST
    1. 转换阶段。会对 AST 进行遍历,在这个过程中对节点进行增删查改。Babel 所有插件都是在这个阶段工作, 比如语法转换、代码压缩。
    1. 输出阶段。将经过转换的AST通过babel-generator再转换成js代码,过程就是深度优先遍历整个AST,然后构建可以表示转换后代码的字符串。同时这个阶段还会生成Source Map

7. webpack plugins的执行时机?

答: 加载文件完成后,输出文件前,不同的plugins有不同的执行时机。

node

1. koa 源码了解过,是怎么实现的?

答: koa通过对http模块的封装,在内部实现了一个context上下文的概念,把res跟req都放在ctx上面,并且对req和res进行优雅的setter/getter处理,调用方式更简单。

洋葱模型通过将中间件数组里的异步方法通过dispatch去递归调用,由于在app.use中去调用next方法时去调用下一个中间件。

洋葱模型实现伪代码

function compose(middlewares){
    return function(){
        return dispatch(0)
        function dispatch(i){
            let fn = middlewares[i]
            if(!fn) return Promise.resolve()
            return Promise.resolve(fn(function next(){
                // promise 完成之后在执行下一个
                return dispatch(i+1)
            }))
        }
    }
}

2. koa 洋葱模型

答: 见上

3. cdn加速是怎么做的?

答: 简单的说就是缓存+负载均衡

  1. 浏览器向域名解析服务器发出解析请求,由于CDN 对域名解析过程进行了调整,所以用户端一般得到的是该域名对应的 CNAME 记录,此时浏览器需要再次对获得的 CNAME 域名进行解析才能得到缓存服务器实际的IP 地址。 注:在此过程中,全局负载均衡DNS 解析服务器会根据用户端的源IP 地址,如地理位置(北京还是上海)、接入网类型(电信还是网通)将用户的访问请求定位到离用户路由最短、位置最近、负载最轻的Cache 节点(缓存服务器)上,实现就近定位。定位优先原则可按位置、可按路由、也可按负载等。
  2. 再次解析后浏览器得到该域名CDN 缓存服务器的实际IP 地址,向缓存服务器发出访问请求。
  3. 缓存服务器根据浏览器提供的域名,通过Cache 内部专用DNS 解析得到此域名源服务器的真实IP 地址,再由缓存服务器向此真实IP 地址提交访问请求。
  4. 缓存服务器从真实IP 地址得到内容后,一方面在本地进行保存,以备以后使用,同时把得到的数据发送到客户端浏览器,完成访问的响应过程。

4. cdn源服务器文件修改,负载均衡还有作用吗?

答: cdn一般用来存静态资源。拿网站来说,当用户访问网站时静态资源从cdn加载。cdn向源服务器请求资源并缓存,这个请求过程是周期性的,自动的,称为回源。 当你更新了一个文件,现在正巧还没到cdn自动更新的时候,如果想让用户马上看到新的就得手动刷cdn,一般cdn控制台都有此选项。

5. 负载均衡有哪些模式

  1. 轮询(默认) 每个请求按时间顺序逐一分配到不同的后端服务器,如果后端服务器down掉,能自动剔除。
  2. weight权重 指定轮询几率,weight和访问比率成正比,用于后端服务器性能不均的情况。权重越高,在被访问的概率越大。
  3. ip_hash 如果客户已经访问了某个服务器,当用户再次访问时,会将该请求通过哈希算法,自动定位到该服务器。
  4. fair(第三方) 按后端服务器的响应时间来分配请求,响应时间短的优先分配。
  5. url_hash(第三方) 按访问url的hash结果来分配请求,使每个url定向到同一个后端服务器,后端服务器为缓存时比较有效。

6. 脱离了nginx怎么配置负载均衡

答: Redis,Zookeeper (ps:nt问题)

7. a服务器node服务怎么访问b服务器的脚本

答: (大佬跟我说的)用RPC RPC是指远程过程调用,也就是说两台服务器A,B,一个应用部署在A服务器上,想要调用B服务器上应用提供的函数/方法,由于不在一个内存空间,不能直接调用,需要通过网络来表达调用的语义和传达调用的数据。

8. 经过网关转发下websocket还可以一直保持心跳吗

答: 不能

9. 前端页面设置强缓存,但是有东西更新了。怎么保证用户的页面是最新的

答:

  1. 静态资源设置协商缓存。
  2. 在静态资源后面配置版本号,时间戳等。
  3. (希望大家可以帮忙提供一下更好的方案)

10. node 事件循环

答:

node中的微观,宏观任务

  1. 常见的 macro-task 比如:setTimeout、setInterval、 setImmediate、script(整体代码)、 I/O 操作等。
  2. 常见的 micro-task 比如: process.nextTick、new Promise().then(回调)等。

microtask 在事件循环的各个阶段之间执行。

  • timers 阶段:这个阶段执行timer(setTimeout、setInterval)的回调
  • I/O callbacks 阶段:处理一些上一轮循环中的少数未执行的 I/O 回调
  • idle, prepare 阶段:仅node内部使用
  • poll 阶段:获取新的I/O事件, 适当的条件下node将阻塞在这里
  • check 阶段:执行 setImmediate() 的回调
  • close callbacks 阶段:执行 socket 的 close 事件回调
  1. timers阶段

timers 阶段会执行 setTimeout 和 setInterval 回调,并且是由 poll 阶段控制的。 同样,在 Node 中定时器指定的时间也不是准确时间,只能是尽快执行。

  1. callbacks阶段 此阶段执行某些系统操作的回调,例如 TCP 错误。

  2. 轮询 poll 阶段

  • 计算应该阻塞并 I/O 轮询的时间
  • 处理轮询队列 (poll queue) 中的事件

当事件循环进入轮询 (poll) 阶段并且没有任何计时器调度 (timers scheduled) 时,将发生以下两种情况之一:

  • 如果轮询队列 (poll queue) 不为空,则事件循环将遍历其回调队列,使其同步执行,直到队列用尽或达到与系统相关的硬限制为止

  • 如果轮询队列为空:如果已通过 setImmediate 调度了脚本,则事件循环将结束轮询 poll 阶段,并继续执行 check 阶段以执行那些调度的脚本。如果脚本并没有 setImmediate 设置回调,则事件循环将等待 poll 队列中的回调,然后立即执行它们。

  1. 检查阶段 check

此阶段允许在轮询 poll 阶段完成后立即执行回调。 如果轮询 poll 阶段处于空闲,并且脚本已使用 setImmediate 进入 check 队列,则事件循环可能会进入 check 阶段,而不是在 poll 阶段等待。

  1. close callbacks 阶段

setImmediate vs setTimeout

setImmediatesetTimeout 相似,但是根据调用时间的不同,它们的行为也不同

  • setImmediate 设计为在当前轮询 poll 阶段完成后执行脚本。
  • setTimeout 计划在以毫秒为单位的最小阈值过去之后运行脚本。

11. node在require的时候发生了哪些事

答:

  1. 拿到要加载的文件绝对路径。没有后缀的尝试添加后缀
  2. 尝试从缓存中读取导出内容。如果缓存有,返回缓存内容。没有,下一步处理
  3. 新建一个模块实例,并输入进缓存对象
  4. 尝试加载模块
  5. 根据文件类型,分类处理
  6. 如果是js文件,读取到文件内容,拼接自执行函数文本,用vm模块创建沙箱实例加载函数文本,获得导出内容,返回内容
  7. 如果是json文件,读取到文件内容,用JSON.parse 函数转成js对象,返回内容
  8. 获取导出返回值。

http

1. http 和 https的区别

答: HTTPS就是将HTTP运行在TLS/SSL的加密安全措施下。

  • https需要申请CA证书
  • https更安全。运用了加密手段
  • https端口443 http是80

2. udp和tcp的区别

答: 见我的另一篇文章TCP/IP

3. http3.0是基于udp的,为什么udp面向无连接还会选择udp?

答: 因为udp高效。而且在应用层解决了udp的不可靠性问题。

4. http3.0怎么解决udp的丢包问题?

答: http3不仅仅只是简单将传输协议替换成了 UDP。还基于 UDP 协议在「应用层」实现了 QUIC 协议。它具有类似 TCP 的连接管理、拥塞窗口、流量控制的网络特性,相当于将不可靠传输的 UDP 协议变成“可靠”的了,所以不用担心数据包丢失的问题。而且, QUIC 协议会保证数据包的可靠性,每个数据包都有一个序号唯一标识。当某个流中的一个数据包丢失了,即使该流的其他数据包到达了,数据也无法被 HTTP/3 读取,直到 QUIC 重传丢失的报文,数据才会交给 HTTP/3。

5. tcp除了你刚刚说的窗口控制,还有哪些控制?

答: 重发控制,流控制,拥塞控制

6. tcp重发机制是基于哪个时间节点

答: 引入两个概念:

  • RTT(Round Trip Time):往返时延,也就是数据包从发出去到收到对应 ACK 的时间。RTT 是针对连接的,每一个连接都有各自独立的 RTT。
  • RTO(Retransmission Time Out):重传超时,也就是前面说的超时时间。

我一般认为是两倍的RTT。

React

react 面试考点见我的react面试考点文章React面试