1.背景
校招进入一家初创型公司,工作刚满两年的前端入门级选手,写过vue,目前主要以React为主。从准备面试到拿到offer经历了将近5个月,大大小小面试20余次,刚开始的时候以为自己准备好了,但是没想到以为的准备好其实还差得远,不得不说,这几个月的面试与学习让我的前端技术突飞猛进,对很多知识的理解变得更加的深入,在此总结一下现在前端常考的一些知识点。有空的时候再把答案填补上~
2.网络及浏览器原理
-
从输入url到渲染出界面发生了什么?
- 浏览器从DNS服务器中进行域名查询,浏览器解析域名拿到对应的IP地址
- 通过IP地址建立TCP请求连接(三次握手)
- 浏览器向服务器发送http请求包,服务器请求处理响应
- 服务器返回HTTP Response后,浏览器开始接收数据,进行资源下载,解析,页面渲染
详细的内容请看:从输入url到看到页面经历了些什么(一)
-
网络七层协议/五层协议?
-
三次握手和四次挥手
三次握手:
- 服务端进程准备好接收来自外部的TCP连接,等待客户端发送请求
- 客户端打开连接,发送信息给服务器端
- 服务器端接收到信息,将接收到信息这个信息回传给客户端,并且为这次通信分配资源
- 客户端收到回传信息,说明通道已经联通了,就将这个信息回传给服务端,并分配资源
- 开始进行通信
三次握手的目的:是同步连接双方的序列号和确认号并交换 TCP 窗口大小信息
四次挥手:
- 客户端发送给服务器端FIN包(告诉它要关闭了)
- 服务器端告诉客户端已经收到准备关闭的消息(这时候可能还有数据没处理完,服务端会继续处理数据)
- 服务器告诉客户端信息已处理完
- 客户端收到回传信息,关闭通信
-
Http1.x,http2.x以及https的区别?https的对称加密和非对称加密
http1.0和http1.1:
- http1.0在1996年开始使用,只是使用在一些简单的网页上,进行简单的网络请求,http1.1在1999年才开始广泛使用,
- http1.1是在http1.0的基础上在缓存处理,带宽优化,错误通知的处理以及host头处理上进行了优化
- http协议运行在TCP之上,默认端口是80,传输的内容都是明文,这个时候就会有安全风险
http2:
- 新的二进制格式(1.x基于文本,2.0基于二进制,实现方便且健壮)
- 多路复用:连接共享,每一个request都是是用作连接共享机制的。一个request对应一个id,这样一个连接上可以有多个request,每个连接的request可以随机的混杂在一起,接收方可以根据request的 id将request再归属到各自不同的服务端请求里面
- header压缩:使用encoder来减少需要传输的header大小,通讯双方各自cache一份header fields表,既避免了重复header的传输,又减小了需要传输的大小
- 服务端推送
https:
- 客户端发起请求,连接到服务器的443端口
- 服务器把自己的信息以数字证书的形式返回给客户端(包括密钥公钥,网站地址,证书颁发机构,失效日期等),数字证书是非对称加密,就是公钥和私钥不一致
- 客户端收到服务器端的响应之后会先验证证书的合法性(看有没有失效,还有网址对不对)
- 浏览器生成随机密码(RSA签名),生成的是一个对称密钥,用服务器给的公钥对这个密钥进行加密,发送给服务器端,如果客户端解密成功,说明服务端确实是私钥的持有者
- 验证完身份之后,客户端生成一个对称加密的算法和对应密钥,以公钥加密之后发送给服务端。之后客户端与服务端可以用这个对称加密算法来加密和解密通信内容了
-
强缓存和协商缓存
强缓存(Expires和Cache-control)
强缓存是利用http头的Expires(http1.0)和Cache-control(http1.1)两个字段进行控制的
- Expires设置一个绝对的失效时间,在那个时间之前发送的请求都读取本地缓存,这个方法的致命缺点在于服务器端和客户端如果时间不一致,特别是时间相差比较大的前提下,就会导致缓存混乱
- Cache-Control吸取了这个经验,它一般通过max-age设置一个时间间隔(3600表示3600秒),表示在这个间隔内的请求都读取本地缓存,优先级比Expires高
Cache-Control除了max-age之外还有几个常用值:
no-cache:不用强缓存,但是可以用协商缓存(协商缓存开启要设置它)
no-store:直接啥缓存都不让用
public:可被所有用户缓存,包括CDN之类的中间代理服务器
private:只允许终端用户缓存
协商缓存
协商缓存会先发送请求到服务器,和服务器商量商量是用本地还是服务器返回,如果本地生效,返回304状态码
- Last-Modified,If-Modified-Since(http1.0)
本地最后一次修改时间,在请求头带上If-Modified-Since,里面包含Last-Modified,服务器校验这个时间和资源修改时间不一致,就会重新返回,否则返回304
这个的弊端在于资源修改频率可能很高,可能会导致无法进行判断;有的修改并非有效修改(可能只改修改时间不改内容),此时其实不希望重新请求;有的服务器并不能精确的得到文件的最后修改时间 - ETag,If-None-Match(http1.1)
ETag是实体标签的缩写,是资源的唯一标识,在第一次请求资源时服务端会返回,再次请求时通过If-None-Match返回给服务器进行比对
-
http常见的状态码
1**:服务器收到请求,需要请求者继续执行操作
101:需要切换协议,表示服务器应客户端升级协议的请求对协议进行切换,就像http访问https的时候2XX:操作被成功接受并处理
200:成功
204:操作成功但是没有任何的返回3XX:重定向,需要进一步的操作以完成请求
301:请求的资源被永久移动到新url里面,在nginx上配置rewrite ^/(.)$ http://XXXX permanent;可实现
302:暂时的重定向,跳转后会保留旧地址,在nginx上配置rewrite ^/(.)$ http://XXXX redirect;可实现
304:客户端的缓存,比如之前的图片,二次加载的时候可能就会有304,避免重新加载4**:客户端错误,因为某种原因导致无法完成请求
401:请求要求用户的身份认证,一般是账号密码不对
403:服务器理解请求客户端的请求,但是拒绝执行此请求,一般是账号密码对了但是这个账号密码没有对应权限
405:请求方法错了,比如前端写的get,但是后端接口写的是post429:请求次数过多,比如某个请求在几秒内触发了上百次
5**:服务器错误,服务器在处理请求过程中发生了某些错误
500:服务器内部错误
502:充当网关或代理的服务器,从远端服务器接收到了一个无效的请求
504:网关超时 -
简单请求和复杂请求
- 简单请求:不能触发CORS预检请求,Content-Type 的值仅限于下列三者之一:text/plain,multipart/form-data,application/x-www-form-urlencoded。包括GET请求(参数从url中带走)以及POST请求(参数通过body带走)
- 非简单请求:先发送预请求options,预请求成功之后才会发送真实请求,OPTIONS的请求是由Web服务器处理跨域访问引发的,包括PUT请求(一般用于文件上传),DELETE请求(删除操作)等
-
XSS和CSRF
-
跨域及解决方案
-
浏览器的进程和线程
进程可以被描述为是一个应用的执行程序,线程存在于进程并执行程序任意部分。如果两个进程需要进行对话,可以通过进程间通信(IPC)来进行。
通常会有疑问,浏览器是单进程还是多进程?
A:浏览器可能是一个拥有很多线程的进程,也可能是一些通过IPC通信的不同线程的进程,Chrome浏览器属于多渲染进程架构。Chrome进程主要包括浏览器进程,渲染进程,插件进程和GPU进程四个部分:
- 浏览器进程:控制应用中的“Chrome”部分,包括地址栏,书签,回退和前进按钮等,还处理一些不可见的操作例如网络请求与文件访问等。
- 渲染进程:控制标签页内的网站展示,简单来说就是每个标签都有自己的渲染进程,这样就算其中一个标签失去响应,也不会影响其他标签的正常使用
- 插件进程:控制站点使用的任意插件
- GPU进程:处理独立于其他进程的GPU任务。因为GPU会被分成不同进程,处理来自不同应用的请求并绘制在相同表面
网络进程:主要负责页面的网络资源加载,之前是作为一个模块运行在浏览器进程里,最近独立出来成为一个单独的进程
多进程的好处:
- 安全性和沙箱化:由于操作系统提供了限制进程权限的方法,浏览器就可以用沙箱保护某些特定功能的进程
- 沙箱看成是操作系统给进程上了一把锁,沙箱里面的程序可以运行,但是不能在你的硬盘上写入任何数据,也不能在敏感位置读取任何数据
多进程的缺点:
- 进程有自己的私有内存空间,所以一些公共部分只能被拷贝不能像线程之间一样共享,所以就会消耗更多内存
-
内存泄漏和垃圾回收
详情:内存管理
-
V8原理,标记整理,全停顿
详情:聊聊V8引擎
-
js异步加载
-
js,css,html渲染阻塞
渲染引擎会先解析HTML头部代码,下载样式表,下载的同时HTML会继续解析,等到解析完成之后,构造生成DOM树,开始解析下载好的CSS,构造CSSOM。
这个时候主线程解析CSS并确定每个DOM节点计算后的样式(没下载完不能开始构建)。样式计算的目的是为了计算DOM节点中的每个元素的具体样式,大体分为三个步骤:- 把CSS转换为浏览器能够理解的结构(渲染引擎接收到CSS文本时,会执行转换操作,将CSS文本转换为styleSheets)
- 转换样式表中的属性值,使其标准化(将不容易被渲染引擎理解的属性标准化,2em->32px这种)
- 计算出DOM树中每个节点的具体样式
等两棵树都解析完了(两棵树是可以并行解析的),就开始进行渲染树的构造。为了加快速度,预加载扫描器(preload scanner)会在主线程处理外部资源请求时同时运行,当HTML文档中有和之类的内容,预加载扫描器会查看由HTML解析器生成的标记,并在浏览器进程中向网络线程发送请求。
如果当中遇到JS代码,DOM的解析就会被迫停止,因为浏览器认为js代码可能会修改DOM结构,所以需要等到js执行完毕之后,DOM才会接着解析还会出现一种特殊情况,就是当CSSOM还没下载完成的时候,HTML解析时遇到js代码,这个时候浏览器会暂停脚本执行,直到CSS下载完,并完成CSSOM构建再继续执行
-
浏览器渲染流程
- 渲染引擎解析HTML文档以及CSS文档,生成对应的DOM和CSSOM
- 两棵树将合成为一棵渲染树,树中有很多的组成部分,这些组成部分被称为节点
- 渲染树构建完毕后,为每个节点分配一个应该出现在屏幕上的确切坐标,这个阶段称为布局
- 找到每个节点的位置后,渲染引擎会遍历渲染树,将每个节点的细节绘制出来
- 最后将画面显示出来
-
重排和重绘
重排:
- DOM树结构的变化(DOM的添加,删除等)
- 请求样式信息:比如offsetTop、offsetLeft、 offsetWidth、offsetHeight、scrollTop、scrollLeft、scrollWidth、scrollHeight、clientTop、clientLeft、clientWidth、clientHeight、getComputedStyle()等,可以在获取时做适当的缓存来减少重排
- 未脱离文档流的情况下改变了元素的位置
- DOM元素的几何属性变化(宽高,margin之类的)
- 元素内容的改变
- 元素的缩放,旋转,或者给元素添加动画
重绘:
8. 背景颜色,字体颜色的改变
9. visibility和opacity这种不会改变宽高,大小的样式 -
浏览器的性能优化
-
如何减少白屏时间
3.js基础
-
this的指向
-
常见的数组方法有哪些
-
'== ’ 和 ’ === '的区别
-
set和map的使用场景
-
var,let,const,变量提升,暂时性死区
-
对作用域的理解
-
闭包和柯里化,高阶函数
-
数据的浅拷贝和深拷贝
function deepClone(obj) { let cloneObj = {}; if (obj === null) return obj; if (obj instanceof Date) return new Date(obj); if (obj instanceof RegExp) return new RegExp(obj); if (typeof obj !== "object") return obj; for (let i in obj) { if (obj.hasOwnproperty(i)) { cloneObj[i] = deepClone[i]; } } return cloneObj; }
-
防抖与节流
// 防抖 function debounce(delay, cb) { let timer; return function (val) { clearTimeout(timer); timer = setTimeout(() => cb(val), delay); }; } // 节流 function throttle(delay, cb) { let timer; return function (arg) { timer = setTimeout(() => { cb(arg); timer = null; }, delay); }; }
-
sleep函数的实现
-
手写promise相关函数
function promiseAll(promises) { return new Promise(function (resolve, reject) { if (!isArray(promises)) { return reject(new TypeError("arguments must be an array")); } var resolvedCounter = 0; var promiseNum = promises.length; var resolvedValues = new Array(promiseNum); for (let i = 0; i < promiseNum; i++) { Promise.resolve(promises[i]).then( function (value) { resolvedCounter++; resolvedValues[i] = value; if (resolvedCounter == promiseNum) { return resolve(resolvedValues); } }, function (reason) { return reject(reason); } ); } }); } function promiseRace(promises) { if (!Array.isArray(promises)) { throw new Error("promises must be an array"); } return new Promise(function (resolve, reject) { promises.forEach((p) => Promise.resolve(p).then( (data) => { resolve(data); }, (err) => { reject(err); } ) ); }); } MyPromise.any = function (promises) { return new Promise((resolve, reject) => { promises = Array.isArray(promises) ? promises : []; let len = promises.length; // 用于收集所有 reject let errs = []; // 如果传入的是一个空数组,那么就直接返回 AggregateError if (len === 0) return reject(new AggregateError("All promises were rejected")); promises.forEach((promise) => { promise.then( (value) => { resolve(value); }, (err) => { len--; errs.push(err); if (len === 0) { reject(new AggregateError(errs)); } } ); }); }); };
-
eventloop,宏任务,微任务
-
正则相关
-
迭代器生成器
-
proxy和defineProperty
4.css基础
- css实现垂直水平居中,多多益善
- css动画的实现
- flex布局
- 如何使用border画出三角形
- BFC和外边框重叠
- 如何做移动端适配
- 有哪些隐藏页面元素的方式
- em/rem/px/vh/vw
5.框架相关(vue & React)
- 两个框架的区别
- vue的nextTick原理
- vue的生命周期/React的生命周期
- vue3的新特性
- diff算法(vue2,vue3,React15,React 16)
- 两个框架的组件通信
- 两个框架的数据流
- fiber原理
- conCurrent mode
- React减少渲染次数的方法(shouldComponentUpdate,pureComponent,memo)
- 类组件和函数式组件的区别
- 常用的hooks有哪些,有没有自己写过hook
- React的事件机制
- vdom的原理和优势
- setState的执行机制,同步还是异步
- 纯函数的含义及优势
6.模块化,组件化和工程化
- 对这三个概念的理解
- commonjs和esmodule的区别
- babel的原理
- polyfill如何做浏览器兼容
- 如何实现组件的按需加载
- babel-import-plugin的原理
- treeshaking原理
- source-map的理解
- rollup和webpack的区别
- webpack中loader和plugin的区别
- loader的执行顺序,手写compose函数
- webpack的打包流程
- splitChunk
- webpack如何进行打包优化
- webpack/rollup的热更新
- node异步编程的原理
- 如何在项目中做代理转发
7.算法
-
排序方法
// 快排 function quickSort(arr) { if (arr.length <= 1) { return arr; } var pivotIndex = Math.floor(arr.length / 2); var pivot = arr.splice(pivotIndex, 1)[0]; //splice剔除中间值,防止判断时重复存入数组 var left = []; var right = []; for (var i = 0; i < arr.length; i++) { if (arr[i] < pivot) { left.push(arr[i]); } else { right.push(arr[i]); } } return quickSort(left).concat([pivot], quickSort(right)); } // 冒泡 function bubbleSort(arr) { let len = arr.length; for (let i = 0; i < len; i++) { for (let j = 0; j < len - 1 - i; j++) { if (arr[j] > arr[j + 1]) { let temp = arr[j + 1]; arr[j + 1] = arr[j]; arr[j] = temp; } } } return arr; } // 插入排序 function insertSort(arr) { let len = arr.length; let index; let curr; for (let i = 1; i < len; i++) { index = i - 1; curr = arr[i]; // 将当前要进行比较的值保存起来 while (index >= 0 && arr[index] > curr) { arr[index + 1] = arr[index]; index--; } console.log(curr, arr, index); arr[index + 1] = curr; } return arr; }
-
二分法
function findArrIndex(arr, val) { let left = 0; let right = arr.length - 1; while (left <= right) { let index = parseInt((right + left) / 2); if (arr[index] === val) { return index; } else if (arr[index] < val) { left = index + 1; } else { right = index - 1; } } }
-
递归/动规
// 汉诺塔 // 递归版 function hanota(A, B, C) { let count = A.length; const move = (n, a, b, c) => { if (n === 1) { c.push(a.pop()); } else { move(n - 1, a, c, b); // a 通过c将n-1个圈圈给b c.push(a.pop()); // 然后把最下面那个圈圈给c move(n - 1, b, a, c); // 接着b通过a把剩下的n个给c // finish } }; move(count, A, B, C); } // 栈版 var hanota = function (A, B, C) { let count = A.length; let stack = [{ count, a: A, b: B, c: C }]; while (stack.length) { let top = stack.pop(); if (top.count === 1) { top.c.push(top.a.pop()); } else { stack.push({ count: top.count - 1, a: top.b, b: top.a, c: top.c }); stack.push({ count: 1, a: top.a, b: top.b, c: top.c }); stack.push({ count: top.count - 1, a: top.a, b: top.c, c: top.b }); } } return; }; // 斐波那契数列 function fibonacci(n) { var a = 1; var b = 1; var c = 0; if (n == 1) { return 1; } else if (n == 2) { return 1; } else { for (var i = 1; i < n - 1; i++) { c = a + b; a = b; b = c; } return b; } } function fibonacci(n) { if (!n) return; let arr = [1, 1]; for (let i = 2; i < n; i++) { let temp = arr[i - 1] + arr[i - 2]; arr.push(temp); } return arr[n - 1]; }
-
栈和队列
-
正则(算是算法编外人员吧)
-
数组的应用(也算编外人员)