前端面试笔记
1. CSS 题目
1.1 画等腰直角三角形
这题是美团前端面试一面题: 参考链接
第一种方法: 使用
boarder
- 第二种方法: 使用
linear-gradient
- 第三种方法: 使用
clip-path
1.2 不定宽高水平垂直居中
- 第一种方法: 使用
Flexbox(也可以父元素是flex然后子元素margin:auto)
- 第二种方法: 使用
Grid
- 第三种方法: 绝对定位 +
transform
1.3 正方形子元素
- 这题是字节跳动一面题: 父元素的宽高不固定,并且可以随窗口变形,而子元素必须保持正方形 (只能使用
CSS)- 解决方案: 子元素使用百分比宽度并使用
aspect-ratio属性来保持子元素的正方形比例
- 解决方案: 子元素使用百分比宽度并使用
1.4 position 定位
position: static(默认定位): 所有元素的默认定位方式。元素按照正常的文档流排列,不会受top、right、bottom或left等属性的影响position: relative(相对定位): 相对于元素自身的原始位置进行定位,但可以通过top、right、bottom、left等属性调整元素的相对位置
position: absolute(绝对定位): 相对于最近的非static定位的父元素进行定位。如果没有非static的父元素,则相对于文档的根元素(通常是<html>或<body>)进行定位
position: fixed(固定定位): 相对于浏览器窗口进行定位,无论页面如何滚动,元素始终保持在浏览器窗口中的固定位置position: sticky(粘性定位): 结合了relative和fixed的特性。元素一开始是相对于文档流定位的(relative),当页面滚动到某个阈值时,元素会变为固定定位(fixed)- 父元素不能
overflow:hidden或者overflow:auto属性 - 必须指定
top、bottom、left、right四个值之一,否则只会处于相对定位
- 父元素不能
1.5 盒模型
盒模型分为IE盒模型和W3C标准盒模型
- 在标准盒模型下,一个块的总宽度 =
width + margin(左右) + padding(左右) + border(左右) - 在
IE盒模型下,一个块的总宽度 =width + margin(左右)(即width已经包含了padding和border值)
当设置
box-sizing:content-box时,采用标准盒模型计算,也是默认模式
当设置box-sizing:border-box时,采用IE盒模型计算
JS如何获取盒模型对应的宽和高window.getComputedStyle(dom).width/height: 取到的是最终渲染后的宽和高dom.getBoundingClientRect().width/height: 得到渲染后的宽和高,还可以取到相对于视窗的上下左右的距离dom.offsetWidth/offsetHeight: 包括高度(宽度)、内边距和边框,不包括外边距 (兼容性最好)
1.6 CSS 实现扇形
1.7 BFC
BFC是CSS布局的一个概念,是一块独立的渲染区域,是一个环境,里面的元素不会影响到外部的元素
2. JS 题目
2.1 var 与循环
- 字节跳动一面的题目
- 下面循环的最终结果是什么,
i的最终值是什么,i在循环结束后的值是什么
for (var i = 0; i < 5; i++) { |
这个循环将会输出
0 1 2 3 4,i的最终值为5由于
var声明的变量在整个函数(或全局范围内)都是共享的,它不受块作用域的限制。因此,i在整个函数或脚本范围内是可见的。换句话说,i仍然在循环结束后存在并且可以访问
for (var i = 0; i < 5; i++) { |
- 这个循环将会输出
5 5 5 5 5,i的最终值为5 - 由于
var是函数级作用域,所有的setTimeout回调函数在执行时访问的都是同一个i,即循环结束时的i值5。因为setTimeout是异步的,它会在1秒后执行,此时循环早已结束,i已经被更新为5 - 解决方案: 使用
let或闭包可以解决这个问题,确保每次迭代时i的值是独立的
2.2 手写倒计时
美团一面的题目
手写一个倒计时函数,要求输出
5 4 3 2 1,每隔一分钟输出一个值方法一: 使用
setIntervalsetInterval会在指定的时间间隔内反复执行一个函数,直到手动清除这个定时器
function countdown() { |
- 方法二: 使用
Generator函数- 使用
Generator函数可以通过yield暂停和恢复函数的执行,结合setTimeout来实现倒计时
- 使用
function* countdownGenerator() { |
2.3 防抖和节流
可以使用类似
loadash这种库来实现防抖
Debounce- 防抖是指在事件被触发
n秒后在执行回调,如果在这n秒内时间又被触发,则重新计时 - 可以使用在一些点击请求的事件上,避免因为用户的多次点击向后端发送多次请求
- 防抖是指在事件被触发
function debounce(fn, delay = 500) { |
防抖函数分为非立即执行版和立即执行版,需要根据不同的场景来决定需要使用哪一个版本的防抖函数
- 节流
Throttle- 节流就是一定时间内执行一次事件,即使重复触发,也只有一次生效
- 可以使用在监听滚动
scroll事件上,通过事件节流来降低事件调用的频率
const throttle = (fn, delay = 500) => { |
const throttle = (fn, delay = 500) => { |
对于节流,一般有两种方式可以实现,分别是时间戳版和定时器版。时间戳版的函数触发是在时间段内开始的时候,而定时器版的函数触发是在时间段内结束的时候
2.4 undefined 与 null 的区别
undefined表示”缺少值”,就是此处应该有一个值,但是还没有定义null表示”没有对象”,即该处不应该有值
2.5 浅拷贝和深拷贝
- 浅拷贝
- 浅拷贝是创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝
- 如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址,所以如果其中一个对象改变了这个地址,就会影响到另一个对象
function shallowCopy(obj) { |
- 深拷贝
- 深拷贝是将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,且修改新对象不会影响原对象
function deepCopy(obj, map = new WeakMap()) { |
2.6 函数柯里化
- 在数学和计算机科学中,柯里化是一种将使用多个参数的一个函数转换成一系列使用一个参数的函数的技术
柯里化实际是把简单的问题复杂化,但是复杂化的同时,在使用函数时拥有了更加多的自由度。而对于函数参数的自由处理,正是柯里化的核心所在。柯里化本质上是降低通用性,提高适用性
- 参数定长的柯里化
- 假设存在一个原函数
fn,fn接受三个参数a, b, c,那么函数fn最多被柯里化三次
- 假设存在一个原函数
function curry(fn) { |
- 参数不定长的柯里化
- 如果要支持参数不定长的场景,已经柯里化的函数在执行完毕时不能返回一个值,只能返回一个函数。同时要让
JS引擎在解析得到的这个结果时,能求出预期的值
- 如果要支持参数不定长的场景,已经柯里化的函数在执行完毕时不能返回一个值,只能返回一个函数。同时要让
function curry(fn) { |
2.7 数组扁平化
实现扁平化的方法,封装
flatten
已有多级嵌套数组[1, [2, [3, [4, 5]]], 6]将其扁平化处理,输出[1,2,3,4,5,6]
ES6 flatflat(depth)方法会按照一个可指定的深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回。使用Infinity,可展开任意深度的嵌套数组
const arr = [1, [2, [3, [4, 5]]], 6] |
⭐ 直接使用自带的方法可以很快的实现, 但是面试官当然不希望就看到这些呀
- 循环递归
- 循环判断数组的每一项是否是数组
Array.isArray(arr[i]) - 是数组就递归调用扁平化代码
result = result.concat(flatten(arr[i])); - 不是数组,直接通过
push添加到返回值数组
- 循环判断数组的每一项是否是数组
function flatten(arr) { |
⭐⭐⭐ 使用递归写出数组扁平化, 但是缺少控制层级关系
- 增加参数控制扁平化深度
- 可以理解为手写
flat()方法
- 可以理解为手写
// forEach 遍历数组会自动跳过空元素 |
⭐⭐⭐⭐ 使用递归写出数组扁平化, 可以通过参数控制层级关系
while循环+some方法- 通过
some来判断数组中是否用数组, 通过while不断循环执行判断, 如果是数组的话可以使用拓展运算符...,...每次只能展开最外层的数组, 加上contact来减少嵌套层数
- 通过
function flatten(arr) { |
⭐⭐⭐⭐ 使用
while循环取消递归操作, 巧用some操作进行判断
2.8 typeof 判断
typeof null是“object”: 历史遗留问题typeof NaN是“number”:NaN实际存储是一种特殊的数值类型typeof Function.prototype是function
| 类型 | 结果 |
|---|---|
| Undefined | "undefined" |
| Null | "object" |
| Boolean | "boolean" |
| Number | "number" |
| BigInt | "bigint" |
| String | "string" |
| Symbol | "symbol" |
| Function (class) | "function" |
| 其他任何对象 | "object" |
2.9 事件委托优化
- 字节一面题
举个例子,比如我们需要去做
Event Tracking System,需要去记录用户在网站内做了什么,点击了什么按钮。如果有很多个按钮,每个都绑一个点击事件性能很差,如何优化?
- 绑定事件到父元素
- 而不是为每个子节点单独绑定事件处理程序,事件委托可以将事件处理程序绑定到公共的父元素上,然后通过事件的
target来确定实际触发事件的子节点。
- 而不是为每个子节点单独绑定事件处理程序,事件委托可以将事件处理程序绑定到公共的父元素上,然后通过事件的
- 检查目标元素
- 使用事件对象的
event.target属性来判断哪个子节点触发了事件,并根据需要处理相应的逻辑
- 使用事件对象的
<ul id="parent"> |
事件委托特别适用于存在大量类似元素的场景,如列表、表格中的行、动态生成的元素等
3. 网络安全
3.1 跨站脚本攻击 XSS
XSS(跨站脚本攻击,Cross-Site Scripting)是一种安全漏洞,攻击者通过向网页注入恶意脚本,使得当用户访问受感染的页面时,恶意脚本会在用户的浏览器中执行- 防止
XSS攻击的关键是严格处理用户输入和输出 - 对用户输入进行严格验证和过滤,使用安全的框架和库
- 防止
3.2 跨站请求伪造 CSRF
- 跨站点请求伪造 (
CSRF) 是一种前端安全攻击,通过伪造的形式来执行你原本不希望执行的操作- 防止
CSRF攻击需要确保请求是合法的,并且是用户有意发起的 - 防止
CSRF攻击的最简单方法之一是使用从服务器生成的CSRF令牌。如果客户端无法提供准确的令牌,服务器可以拒绝请求的操作
- 防止
4. 网络协议
4.1 网络七层模型与四层模型区别
- 参考: 链接
- 网络七层模型
OSI(Open Systems Interconnection Model)是一个标准,而非实现
OSI模型是从上往下的,越底层越接近硬件,越往上越接近软件,这七层模型分别是物理层、数据链路层、网络层、传输层、会话层、表示层、应用层。
> 物理层:底层数据传输,如网线;网卡标准。 |
- 网络四层模型是一个实现的应用模型,由七层模型简化合并而来
TCP/IP模型将OSI模型由七层简化为四层,传输层和网络层被完整保留,因此网络中最核心的技术就是传输层和网络层技术
4.2 http 和 https 基本概念
HTTP: 是互联网上应用最为广泛的一种网络协议,是一个客户端和服务器端请求和应答的标准TCP,用于从WWW服务器传输超文本到本地浏览器的传输协议,它可以使浏览器更加高效,使网络传输减少HTTPS: 是以安全为目标的HTTP通道,简单讲是HTTP的安全版,即HTTP下加入SSL层,HTTPS的安全基础是SSL,因此加密的详细内容就需要SSLHTTPS协议的主要作用可以分为两种:一种是建立一个信息安全通道,来保证数据传输的安全。另一种就是确认网站的真实性
4.3 http 和 https 区别
HTTP协议传输的数据都是未加密的,也就是明文的,因此使用HTTP协议传输隐私信息非常不安全,为了保证 这些隐私数据能加密传输,于是网景公司设计了SSL(Secure Sockets Layer)协议用于对HTTP协议传输的数据进行加密,从而就诞生了HTTPS。简单来说,HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,要比HTTP协议安全
https协议需要到ca申请证书,一般免费证书较少,因而需要一定费用http是超文本传输协议,信息是明文传输,https则是具有安全性的ssl加密传输协议http和https使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443http的连接很简单,是无状态的。https协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比http协议安全
注意:
HTTPS = HTTP + SSL/TLS,如今SSL已废弃,所以现在只关注HTTP + TLS
4.4 http1.x 和 http2.x 区别
http1.x和http2.x主要有以下4个区别
HTTP2使用的是二进制传送,HTTP1.X是文本(字符串)传送- 二进制传送的单位是帧和流。帧组成了流,同时流还有流
ID标示 - 优势: 传输速度更快 (二进制数据体积较小), 处理更高效 (不需要进行文本解析), 适用于复杂数据类型 (图像、音频、视频), 安全性更高 (二进制数据不易被直接阅读)
- 二进制传送的单位是帧和流。帧组成了流,同时流还有流
HTTP2支持多路复用- 因为有流
ID,所以通过同一个http请求实现多个http请求传输变成了可能,可以通过流ID来标示究竟是哪个流从而定位到是哪个http请求
- 因为有流
HTTP2头部压缩HTTP2通过gzip和compress压缩头部然后再发送,同时客户端和服务器端同时维护一张头信息表,所有字段都记录在这张表中,这样后面每次传输只需要传输表里面的索引ID就行,通过索引ID查询表头的值
HTTP2支持服务器推送HTTP2支持在未经客户端许可的情况下,主动向客户端推送内容
4.5 http 请求方式
http请求方式有以下8种,其中GET和POST是最常用的
GET: 向特定的资源发出请求。GET方法不应当被用于产生“副作用”的操作中POST: 向指定资源提交数据进行处理请求, 例如提交表单或者上传文件。数据被包含在请求体中。POST请求可能会导致新的资源的建立和/或已有资源的修改PUT: 向指定资源位置上传其最新内容DELETE: 请求服务器删除Request-URL所标识的资源HEAD: 向服务器索与GET请求相一致的响应,只不过响应体将不会被返回。这一方法可以再不必传输整个响应内容的情况下,就可以获取包含在响应小消息头中的元信息OPTIONS: 返回服务器针对特定资源所支持的HTTP请求方法,也可以利用向web服务器发送‘*’的请求来测试服务器的功能性TRACE: 回显服务器收到的请求,主要用于测试或诊断CONNECT:HTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器
4.6 TCP 三次握手
5. React 题目
6. 缓存
6.1 强缓存与协商缓存
参考: 链接
为了减少资源请求次数,加快资源访问速度,浏览器会对资源文件如图片、
css文件、js文件等进行缓存,而浏览器缓存策略又分为强缓存和协商缓存强缓存
Strong Cache- 所谓强缓存,可以理解为强制缓存的意思,即浏览器在访问某个资源时会判断是否使用本地缓存里已经存在的资源文件,使用本地缓存的话则不会发送请求到服务器,从而达到减轻服务器访问压力的作用,且由于直接从本地缓存读取资源文件,大大提高了加载速度
- 浏览器第一次请求远程服务器的某个资源时,如果服务器希望浏览器得到该资源后一段时间内不要再发送请求过来,直接从浏览器里的缓存里取,则服务器可以通过在响应头里设置
Cache-Control: max-age=31536000,max-age代表缓存时间,单位为秒,这里的数据换算过来就是一年,意味着在一年内浏览器不会再向服务器发送请求。
- 使用缓存的话,状态码
200后面会标明情况。浏览器缓存资源的地方有两个: 磁盘缓存(disk cache)和内存缓存(memory cache)
- 当缓存时间到期后再次访问时,状态码
200后面便没有括号内的内容了
一般来说,浏览器会将较大的资源缓存到
disk cache,而较小的资源则被缓存到memory cache里。内存缓存与磁盘缓存相比,访问速度要更快一些
- 强缓存除了使用
Cache-Control实现之外,还可以使用Expires字段,Expires是Http1.0规范,Cache-Control是Http1.1规范,Expires返回一个具体的时间值,代表缓存的有效期,在该日期内浏览器不会向服务器发起请求,而是直接从缓存里获取资源
- 因为
Expires参照的是本地客户端的时间,而客户端的时间是可以被修改的,所以会有误差产生的情况,这也是Expires的一个缺点,所以有了后来Http1.1规范的Cache-control
Cache-control的优先级要高于Expires,如果两者同时设置,会优先使用Cache-control而忽略掉Expires
- 协商缓存
Negotiation Cache- 在强缓存里,是否使用缓存是由浏览器来确定的,而协商缓存则是由服务器来告诉浏览器是否使用缓存资源,也就是浏览器每一次都要发送请求到服务器询问是否使用缓存
- 浏览器初次请求资源,服务器返回资源,同时生成一个
Etag值携带在响应头里返回给浏览器,当浏览器再次请求资源时会在请求头里携带If-None-Match,值是之前服务器返回的Etag的值,服务器收到之后拿该值与资源文件最新的Etag值做对比
如果没有变化则返回
304,告诉浏览器继续使用缓存(不返回资源文件)
如果发生变化,则返回200和最新的资源文件给浏览器使用
- 除了
Etag外,还有一个Last-Modified的属性,它是Http1.0规范的,服务器返回Last-Modified,浏览器请求头对应携带的是If-Modified-since,与Etag不同的是,Last-Modified的值是一个时间值,代表文件的修改时间,服务器通过对比文件的修改时间是否发生改变来判断是否使用缓存
- 相比
Last-Modified,Etag优先级更高,使用上也更精确一些,因为有时候会存在文件内容并没有改变,但文件的修改时间变更了,Last-Modified不一致所以服务器会重新返回资源文件,实际上还是可以继续使用缓存的
强缓存优先级大于协商缓存,即两者同时存在时,如果强缓存开启且在有效期内,则不会走协商缓存
7. 算法
7.1 LRU Cache
- 字节一面算法题
- 使用
JS实现一个魔改版的LRU Cache,并且满足以下要求
维护一个容量为
n的缓存
每个缓存项如果在X秒后没有被使用,则自动删除
- 可以使用
Map来维护缓存的顺序和容量,利用setTimeout来实现自动删除的功能
class LRUCache { |
7.2 格式化数字
字节一面算法题
给一个数字比如
1000000,把它转化成1,000,000。或者是1000000.12,把它转化成1,000,000.12。只能使用JS实现解决方案一: 使用
toLocaleString()
function formatNumberWithCommas(number) { |
- 解决方案二: 正则表达式
function formateNumberWithCommas(number) { |
- 解决方案三: 手动实现格式化
function formatNumberWithCommas(number) { |
7.3 手写数组转树
- 做到类似下面的转换
let input = [ |
function arrayToTree(array) { |
7.4 数组去重
将
[1,1,2,2,3,3,4,4,5,5]去重, 结果应该是[1,2,3,4,5]方法一:
ES6的Set去重
const arr = [1,1,2,2,3,3,4,4,5,5]; |
Set去重有一个弊端,无法去重引用类型的数据。比如对象数组[{a:1}, {a:1}]
- 方法二: 双重
for循环去重
const handleRemoveRepeat = (arr) => { |
使用
len = arr.length的原因: 假设这个循环需要循环10000次,length就会被执行10000次
附录
文件还未上传 Github




