第二套 阿里面试题
一面
vue/react 开发与 jQuery 开发有什么区别
vue/react 是框架,jquery 是库;
数据与视图分离 jquery 数据与视图没有分离,是混在一起的。
数据驱动视图 通过数据去驱动视图变化,只关心数据的变化。
vue/react 双向数据绑定,监听依赖属性,观察者模式,依赖属性发生改变,重新渲染。而 jquery 要获取 dom 对象,数据发生变化后,还要手动给 dom 对象进行值的操作等。
vue/react 的 dom diff 过程,二者有什么差异
vue 和 react 的 diff 算法,都是忽略跨级比较,只做同级比较。vue diff 调用 path(), 参数是 vnode 和 oldVnode,分别代表新旧节点。
1.vue 对比节点,当节点相同,但是 className 不同,代表是不同元素,会删除重建。而 react 会认为是同类型节点,只会修改节点属性。
- vue 的 diff 对比,采用从两端到中间的对比方式,而 react 则采用从左到右依次比对的方式。
当一个集合,只是把最后一个节点移动到了第一个,react 会把前面的节点依次往后移动,而 vue 只会把最后一个节点移动到第一个上。总体上,vue 的对比方式更高效。
react hooks 的 useState 相对于有状态组件 state 区别
DNS 解析流程
先看浏览器是否缓存 ip,有缓存,直接取 ip;
本机缓存;
hosts 文件;
路由器缓存;
ISP DNS 缓存;(互联网服务提供商)
去根域名服务器查询,根域名服务器只维护后缀,如.net, .com, ,cn。 会返回根域名 ip 给 DNS;
再去 TLD (top level domain 顶级域名)服务器查询,如 google.com, 会返回 IP,同样它也是将 ip 返回给 NDS;
DNS 拿到 ip 去名称服务器上查询,返回 IP;
DNS 收到响应后,先缓存在本地,再返回给客户端;
TCP/IP 协议分层,三次握手四次挥手的状态码,服务端连接队列拥堵时怎么处理,客户端第四次挥手后为什么要等待 2MSL 时间才转换为 close 状态
TCP/IP 协议分层
- 应用层: HTTP, FTP, DNS
- 传输层: TCP, UDP
- 网络层: ip
- 链路层: 网卡,Mac 上网地址
- 物理层: 网线
三次握手四次挥手的状态码
- SYN 建立连接 (synchronous)
- ACK 确认 (acknowledgement)
- PSH 传送 data 数据 (push)
- FIN 结束连接 (finish)
- RST 连接重置,用来强制的断开连接 (reset)
- URG 紧急 (urgent)
服务端连接队列拥堵时怎么处理
客户端第四次挥手后为什么要等待 2MSL 时间才转换为 close 状态
为了保证客户端最后一个 ACK 报文能够成功到达服务器。因为 ACK 可能丢失,从而导致服务器接受不到最后一次的 ACK。超时后,服务器会重发 FIN,接着客户端在重传一次确认。再次重新启动计时器。如果没有等待 2MSL 的话,那么客户端发送完 ASK 就关闭,一旦 ACK 丢失的话,那么服务器无法进入正常的关闭连接状态。
它还可以防止已失效的报文。客户端在发送最后一个 ACK 之后,再经过 2MSL,就可以使本链接持续时间内所产生的所有报文段都从网络中消失。从而保证在关闭连接后不会有还在网络中滞留的报文段去骚扰服务器。
MSL: 报文在网络中存活的最长时间。
为什么是 2MSL 呢?
去向 ACK 消息最大存活时间 + 来向 FIN 消息最大存活的时间
网络通信中引入滑动窗口的作用
有了滑动窗口,通信双方就不用发送一个报文后,收到此报文的确认后再发送下一个报文,而是可以连续发送多个报文,只要别超过窗口的大小限制;
TCP 开销比 UDP 大,一旦网络拥塞或报文丢失就会造成报文重发,而这些重发又加重了拥堵,所以 TCP 里要严格控制发送速率以防止网络拥塞,滑动窗口根据接收方的 win 大小很好限制了发送方的发送速率。
作用:
- 滑动窗口允许发送发连续发送多个报文
- 根据对方接收窗口大小限制发送方的发送速率,防止拥塞
http1.1/2/3 差异,有做什么优化
http1.1
- 默认长连接 keep-alive
- 管道化, 可以不等第一个请求响应继续发送后面的请求,但是响应顺序还是按照请求顺序返回
- 断点续传
- 缓存新增 cache-control
http2.0
- 二进制分帧:所有的传输采用二进制格式编码
- 头部压缩
- 多路复用(只解决了 http1.1 管道化的对手阻塞。但是 TCP 协议上的阻塞还是存在的。)
- 服务器推送
- 伪头字段
- 默认使用加密
http3.0
是谷歌搞出来的
- 基于 UDP,不需要三次握手,减少了握手延迟
- 多路复用,解决了队首阻塞的问题 (即使丢失了包,也不会影响后面的。在组装包的时候,若是发现少了包,那就要求重传。)
https 详细流程,为什么最后会使用对称加密,而不是全过程使用非对称加密
https 流程:
客户端发起一个 https 请求,连接到服务器的 443 端口,向服务器协商协议与所支持的算法。
服务器把自己的信息以数字证书的形式返回给客户端。(证书内容有密钥公钥,网络地址,证书颁发机构,失效日期等)证书中有一个公钥来加密信息,私钥由服务器持有。
验证证书的合法性。客户端收到服务器的响应后会先验证证书的合法性。(证书中的包含的地址与正在访问的地址是否一致,是否有效)
随机生成密钥。如果验证通过,或用户接受了不受信任的证书,浏览器会生成一个随机的对称密钥并用公钥加密,让服务器用私钥解密。解密后就可以用这个对称密钥进行传输了。
生成对称加密算法。验证完服务器身份后,客户端生成一个对称加密的算法和对应密钥,以公钥加密发送给服务端。之后服务器和客户端可以用这个对称加密算法来加密和解密通信内容了。

为什么最后会使用对称加密,而不是全过程使用非对称加密
非对称加密解密算法效率较低,不适用于客户端和服务端这样高频率的通信过程,在某些极端情况下,可能会比对称加密慢上 1000 倍。
非对称加密的优势在于可以很好的帮助完成秘钥的交换,所以前期交换秘钥可以用非对称加密。
服务器如何得知浏览器发送了 http 请求
服务器会提供一个端口,如 http 是 80, https 是 443,监听端口是否被访问。
若有人访问,框架层会解析 path,如果 path 在路由控制器中有被定义,那么执行回调函数,如果没有的话就返回 404
// 比如 next.js
nestController.router('/api/getUserList', () => {
// 逻辑处理
// 数据库操作
// 向浏览器返回结果
});
服务端高并发问题怎么解决
前端方面:(主要是其他方面我也不会了…)
合并 ajax 请求,当需要一些数据时,需要同时调用几个接口,这时候如果可以,让后台或者 Node 中间层合并,调用 1 个接口即可达到同样的效果;
合并 CSS、JS;
压缩 CSS、JS、图片;
使用 CDN 加速;
允许浏览器缓存一些静态资源;
建立图片服务器,页面静态化;
redis 如何得知缓存失效
xss/csrf/SQL 注入,在前端和后台分别要做什么安全防范工作
xss (Cross Site Scripting) 跨站脚本攻击
验证用户提交的表单,只接收规定的长度和类型,过滤其他的输入内容。
对数据进行 html encode 处理
过滤 javascript 事件的标签,如 onclick, onerror, onfocus 等
csrf (Cross Site request Forgery) 跨站请求伪造
请求带上 referer,就可以知道是从哪个网站发起的请求,只有同域名才允许访问;
加上 token 验证,可以拿到 cookie,但是拿不到 token
加上验证码校验
禁止其他网站携带本网站的 cookie 信息, same-site 属性
sql 注入
普通用户与系统管理员用户的权限要有严格的分明
强迫使用参数化语句
加强对用户输入的验证
从获得 HTML 到解析页面全流程,为什么栅格线程使用 GPU 计算而不是 CPU 计算
- 根据 HTML 生成 DOM 树
- 根据 CSS 生成 CSS 规则树
- 结合 DOM 树和 CSSOM 树,生成渲染树
- 根据渲染树计算每一个节点的信息
- 根据计算好的信息,绘制页面
因为 GPU 就是图形处理器,不需要它爸爸出手。
影响首屏加载的因素有哪些,分别如何进行优化,performance 有哪些相关的指标
- 网络
- 服务端性能
- 前端页面设计
- 资源下载执行时间
优化
- 使用 CDN 加速
- 图片懒加载,首屏加载
- 样式放在顶部,js 在为底部。或者使用 async 方式载入,避免 js 阻塞 js 渲染
- 尽量少的引用各大库,为了某个特效就引用一堆 css 和 js 文件很不明智
- 服务器开启 GZIP 压缩,支持本地缓存
相关指标
- TTFP: 首字节时间 「time to first byte」
- FP: 首次绘制(第一个节点)「first paint」
- FCP: 首次有内容的绘制(骨架) 「first contentful paint」
- FMP: 首次有意义的绘制(包含所有元素/数据) 「first meaningful paint」
- TTI: 达到可交互时间 「time to interactive」
js 原型链/执行上下文/闭包/eventloop 机制,class 的继承和 prototype 是完全一样的吗,new 操作符做了哪些事情
原型链
javascript 中除了 null 以外都具备的属性,指向其构造函数的原型属性。当查找实例的属性时,如果自身找不到,就会通过proto,查找原型的属性,如果还找不到,就找原型的原型。通过proto,连接起来的,就被成为原型链。
执行上下文
执行上下文内存储包括了:变量对象,作用域链,this 指向;
全局执行上下文:以浏览器为例,就是指向 window
函数执行上下文:每当函数调用时,都会创建一个新的函数执行上下文。就是指 this,谁调用它,this 就指向谁。
Eval 执行上下文:Eval 函数特有的
闭包
闭包:可以读取其他函数内部的变量的函数,这个函数不会被垃圾回收机制回收。
注意:不要滥用闭包,因为会使得函数内的变量一直保存在内存中,这样就会造成内存泄漏。
eventLoop 机制
EventLoop 就是事件循环,是浏览器与 Node 异步调度的过程。异步队列里分微任务和宏任务,先把所以微任务执行完,再执行宏任务。当执行宏任务时,若遇到了微任务,先把微任务跑完,再接着跑下一个宏任务。这一点 Node 跟浏览器有点不同,Node 版本小于 11.0 时,当执行宏任务时,遇到了微任务,不管微任务,先把宏任务跑完再跑微任务。在 Node11.0 开始与浏览器统一。
class 继承与 prototype 继承是完全一样的吗?
ES5 prototype 继承:
- 通过原型链实现继承。子类的 prototype 指向父类对象的一个实例,因此子类的原型对象包含指向父类的原型对象的指针,父类的实例属性成为了子类原型属性。
- 实质是创造子类的实例对象 this,再将父类的方法添加到 this 上 「Person.apply(this)」
function Parent(name, age) {
this.name = name;
this.age = age;
}
function Child(...args) {
Parent.apply(this, args);
}
let child = new Child();
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;
ES6 class extends 继承
子类没有自己的 this 对象,必须在 constructor 中通过 super 继承父类的 this 对象,而后对此 this 对象进行加工。super 关键字在构造函数中表示父类的构造函数,用来新建父类的 this 对象。
实质是先创建父类的实例对象 this,在用子类的构造函数修改 this。
class Parent {
constructor(name, age) {
this.name = name;
this.age = age;
}
}
class Child extends Parent {
constructor(...args) {
super(...args);
}
}
new 操作符做了哪些事情
- 创建一个空对象
- 将新对象的
__proto__指向构造函数的 prototype - 执行构造函数,并且更改作用域到新对象
- 返回新对象
概括来说是:新建了一个空对象,这个对象的__proto__指向构造函数的 prototype,执行构造函数后返回这个对象。
js 常见几种异步代码编写的方式对比(promise/generator/async-await/rxjs)
promise
- promise 是 ES6 推出的一种异步解决的方案。
- 当从等待状态变成其他状态时,就永远不能再更改状态了。
- promise 链式调用,每次返回的都是一个新的 Promise 实例,会讲结果一直传递下去。
function promise1() {
console.log('promise1 run');
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve();
}, 1000);
});
}
function promise2() {
console.log('promise1 run2');
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve();
}, 1000);
});
}
function normal() {
console.log('normal');
}
promise1().then(promise2).then(normal);
generator
- generator/yield 也是 ES6 推出的。
- 声明时要在 function 后面加上*, 并在函数内配合使用关键字 yield。
- yield 可暂停函数,next 可启动,每次返回的 value 是 yield 后的表达式结果。
- yield 表达式本身没有返回值,或者说总是返回 undefined.next 方法可以带一个参数,当做是上一个 yield 表达式的返回值。
- done 如果碰到 return 或者执行完成,则返回 true,否则返回 false。
function* foo(x) {
let y = 2 * (yield x + 1);
let z = yield y / 3;
return x + y + z;
}
let it = foo(5);
console.log(it.next()); // {value: 6, done: false}
console.log(it.next(12)); // {value: 8, done: false}
console.log(it.next(13)); // {value: 42, done: true}
注意!若 next 没有传值进去,那么上一个 yield 永远返回 undefined
async/await
- async/await 是基于 Promise 实现的,它不能用于普通的回调函数。
- 跟 Promise 一样,是非阻塞的。
- async/await 使异步代码看起来像同步代码。
- 如果一个函数用了 async, 那么改函数就会返回一个 Promise
js 内存回收机制,如何避免内存泄漏
js 内存回收机制
javascript 具有自动垃圾回收机制,会定期对那些我们不再使用的变量、对象所占用的内存进行释放。
垃圾回收的两种实现方式
- 标记清除
当变量进入执行环境时,被标记为“进入环境”,当变量离开执行环境时,则标记“离开环境”。 被标记为“进入环境”,是不能被回收的,应为正在使用。“离开环境”则表示可以被回收。
function test() {
const a = 1;
const b = 2;
// 函数执行时,a b 分别被标记为进入环境
}
test(); // 函数执行完毕,a b 被标记为离开环境,被回收
- 引用计数
统计引用类型变量被引用的次数,次数为 0,即可被回收。 但是循环引用的话,计数永远无法为 0。只能手动清除。
function test() {
const c = {}; // 引用类型变量 c的引用计数为 0
let d = c; // c 被 d 引用 c的引用计数为 1
let e = c; // c 被 e 引用 c的引用计数为 2
d = {}; // d 不再引用c c的引用计数减为 1
e = null; // e 不再引用 c c的引用计数减为 0 将被回收
}
function test1() {
let f = {};
let g = {};
f.prop = g;
g.prop = f;
// 由于g和f 互相引用,所以计数无法为零
}
// 只能手动进行内存释放
f.prop = null;
g.prop = null;
在现代浏览器中,Javascript 使用的是标记清除,所以不用担心循环引用问题。
如何避免内存泄漏
- 闭包
- 全局变量,因为全局变量只有当页面被关闭时,才会被销毁。记得在使用后,对其重新赋值为 null
- 未销毁的定时器,与在定时器里的变量也无法被回收
- dom 引用,即使做了移除 dom 的操作,但是 dom 一旦被引用了,就不会被回收
v8 引擎解释执行 js 代码的详细流程
- 解析器 Parser, 先词法解析,将字符流(char stream)转化成标记流 (token stream)。
- 再语法分析,将前面生成的 token 流根据语法规则,形成一个有元素层级嵌套的语法规则树,即抽象语法树 AST
- 解释器 Ignition 生成字节码
- 执行代码及优化
WebAssembly 有听说过吗?它的优点是什么,使用时需要注意哪些问题
WebAssembly(又名 wasm)是一个运行在网络浏览器中的高效的、低级别的字节码。WASM 使得你可以使用 JavaScript 以外的语言(C,C++,Rust 等),用它们编写代码,然后提前编译成 WebAssembly。
这所带来的好处的就是可以非常快的加载和运行 web 程序。
优点:
运行速度很快,只比原生慢 20%;尤其是高计算量、对性能要求要的应用场景,如图像/视频解码,图像处理,3D/AR 等。
遇到不兼容 WebAssembly 的浏览器,那可能就要做降级处理了。
项目中可以通过什么方法来避免出现错误
二面
服务端如何得知客户端的 http 请求已发送完毕
content-length
客户端如何得知服务器的数据已传输完毕
请求在客户端报 504gateway timeout 时,在服务端看到的状态码是什么
- 504 网关超时,Gateway timeout 是 nginx 报的错,一般是 nginx 作为反向代理服务器时,所连接的应用服务器,如 tomcat 无响应。
请求在客户端报 413 是什么错误,怎么解决
413,说明上传的文件大小超过服务器限制。
做切片
docker 部署项目的完整流程
cookie、session storage、local storage 差异
- cookie 数据始终在同源的 http 请求中携带(即使不需要),即 cookie 在浏览器与服务器间来回传递。session storage 和 local storage 仅保留在客户端内,不与服务器通信。
- cookie 一般由服务端生成,可以设置失效时间。若由客户端生成,则关闭浏览器后失效。session storage 仅在当前会话生效,关闭页面或浏览器失效。local storage 是存储在本地,需要手动清除。
- cookie 小于 4k,session storage 和 local storage 5M 左右。
- cookie 调用的时候还要自己封装,session storage 和 local storage 使用比较方便。
restful API 的官方定义是什么
目前比较成熟的一套互联网应用程序的 API 设计理论,接口命名风格